/ / Il modo migliore per gestire le interfacce nella risposta HTTP - json, rest, go

Il modo migliore per gestire le interfacce nella risposta HTTP: json, resto, via

Sto usando un'API che formatta le sue risposte in questo modo:

{
"err": 0,
"data": **Other json structure**
}

Il modo in cui sto ricevendo una risposta in questo momento è che sto inserendo la risposta in una struttura come questa:

type Response struct {
Err int        `json:"err"`
Data interface{} `json:"data"`
}

e poi lo sto facendo dopo aver ricevuto una risposta

jsonbytes, _ := json.Marshal(resp.Data)
json.Unmarshal(jsonBytes, &dataStruct)

Sto solo ignorando gli errori per questo esempio.
Mi sembra un po 'strano che io stia facendo il marshalling e il unmarshaling quando so come dovrebbero essere i dati e che tipo dovrebbero essere.

C'è una soluzione più semplice che non vedo o è una cosa normale da fare?

Modifica: dovrei probabilmente menzionare che l'attributo Data nell'oggetto risposta può variare a seconda della chiamata API che sto facendo.

risposte:

2 per risposta № 1

Il unmarshaller JSON utilizza la riflessione per esaminare il tipo a cui non sta eseguendo il marshalling. Dato un non inizializzato interface{} come destinazione per i dati senza marshalling, un JSON object viene smascherato in a map[string]interface{} (esempio nel parco giochi).

Ecco alcune idee.

Se si conosce il tipo di dati, è possibile definire una nuova struttura di risposta per ciascun tipo. Esempio:

type FooResponse struct {
Err  int `json:"err"`
Data Foo `json:"data"`
}

type Foo struct {
FooField string `json:"foofield"`
}

type BarResponse struct {
Err  int `json:"err"`
Data Bar `json:"data"`
}

type Bar struct {
BarField string `json:"barfield"`
}

Opzione B

Se preferisci avere un singolo Response struct invece di uno per tipo, puoi dire al unmarshaller JSON di evitare di smascherare il file data fino a un momento successivo utilizzando il json.RawMessage tipo di dati:

package main

import (
"encoding/json"
"fmt"
"log"
)

type Response struct {
Err  int             `json:"err"`
Data json.RawMessage `json:"data"`
}

type Foo struct {
FooField string `json:"foofield"`
}

type Bar struct {
BarField string `json:"barfield"`
}

func main() {
fooRespJSON := []byte(`{"data":{"foofield":"foo value"}}`)
barRespJSON := []byte(`{"data":{"barfield":"bar value"}}`)

var (
resp Response
foo  Foo
bar  Bar
)

// Foo
if err := json.Unmarshal(fooRespJSON, &resp); err != nil {
log.Fatal(err)
}
if err := json.Unmarshal(resp.Data, &foo); err != nil {
log.Fatal(err)
}
fmt.Println("foo:", foo)

// Bar
if err := json.Unmarshal(barRespJSON, &resp); err != nil {
log.Fatal(err)
}
if err := json.Unmarshal(resp.Data, &bar); err != nil {
log.Fatal(err)
}
fmt.Println("bar:", bar)
}

Produzione:

foo: {foo value}
bar: {bar value}

https://play.golang.org/p/Y7D4uhaC4a8

Opzione C

Una terza opzione, come sottolineato da @mkopriva in un commento sulla domanda, è di usare interface{} come tipo di dati intermedio e preinizializzarlo su un tipo di dati noto.

L'enfasi si trova sulla parola intermediario - ovviamente passando per un interface{} è meglio evitare (Go Piver "Proverbi). Il caso d'uso qui è quello di consentire l'utilizzo di qualsiasi tipo di dati senza la necessità di più diversi Response tipi. Sulla strada per evitare di esporre interface{} è avvolgere completamente la risposta, esponendo solo i dati e l'errore:

package main

import (
"encoding/json"
"fmt"
"log"
)

type Foo struct {
FooField string `json:"foofield"`
}

type Bar struct {
BarField string `json:"barfield"`
}

type Error struct {
Code int
}

func (e *Error) Error() string {
return fmt.Sprintf("error code %d", e.Code)
}

func unmarshalResponse(data []byte, v interface{}) error {
resp := struct {
Err  int         `json:"err"`
Data interface{} `json:"data"`
}{Data: v}

if err := json.Unmarshal(data, &resp); err != nil {
return err
}

if resp.Err != 0 {
return &Error{Code: resp.Err}
}

return nil
}

func main() {
fooRespJSON := []byte(`{"data":{"foofield":"foo value"}}`)
barRespJSON := []byte(`{"data":{"barfield":"bar value"}}`)
errRespJSON := []byte(`{"err": 123}`)

// Foo
var foo Foo
if err := unmarshalResponse(fooRespJSON, &foo); err != nil {
log.Fatal(err)
}
fmt.Println("foo:", foo)

// Bar
var bar Bar
if err := unmarshalResponse(barRespJSON, &bar); err != nil {
log.Fatal(err)
}
fmt.Println("bar:", bar)

// Error response
var v interface{}
if err := unmarshalResponse(errRespJSON, &v); err != nil {
log.Fatal(err)
}
}

Produzione:

foo: {foo value}
bar: {bar value}
2009/11/10 23:00:00 error code 123

https://play.golang.org/p/5SVfQGwS-Wy