2016-02-09 3 views
6

Lassen Sie uns sagen, dass ich die folgende Go Struktur auf dem ServerWie kann man dem Client mitteilen, dass er eine Ganzzahl anstelle einer Zeichenfolge von einem Go-Server senden muss?

type account struct { 
    Name string 
    Balance int 
} 

Ich möchte json.Decode auf der eingehenden Anfrage nennen es auf ein Konto zu analysieren.

var ac account 
    err := json.NewDecoder(r.Body).Decode(&ac) 

Wenn der Client sendet die folgende Anfrage:

{ 
    "name": "[email protected]", 
    "balance": "3" 
} 

Decode() wird die folgende Fehlermeldung zurück:

json: cannot unmarshal string into Go value of type int 

Jetzt ist es möglich in „Sie, dass zurück zu analysieren hat eine Zeichenfolge für Balance gesendet, aber Sie hätten eigentlich eine Ganzzahl senden sollen ", aber es ist schwierig, weil Sie den Feldnamen nicht kennen. Es wird auch viel schwieriger, wenn Sie viele Felder in der Anfrage haben - Sie wissen nicht, welche nicht analysiert wurde.

Was ist die beste Methode, um diese eingehende Anforderung in Go zu übernehmen und die Fehlermeldung "Balance muss eine Zeichenfolge sein" für eine beliebige Anzahl von Ganzzahlfeldern in einer Anforderung zurückzugeben?

+0

Ich denke, Sie so etwas wie dekodieren auf eine generische Struktur voll von Schnittstellen und Nutzungsart Behauptung tun könnte, um zu sehen, ob alle int Felder wie Ints aussehen . –

+3

Es gibt ein offenes Problem, um mehr Informationen aus Dekodierfehlern zu erhalten, aber es ist nicht trivial und würde nicht vor mindestens go1.7 passieren. Können Sie einfach json.Unmarshaler implementieren und die Felder validieren? – JimB

+1

Letztendlich: Wenn der Client eine ungültige Anforderung sendet, sollten Sie eine 400 Bad Request-Nachricht zurückgeben und Ihre API-Dokumentation zur Verfügung stellen, damit sie wissen, wie es falsch ist. –

Antwort

0

Es scheint, dass here eine Implementierung bietet, um dieses Problem nur in Go zu umgehen.

type account struct { 
    Name string 
    Balance int `json:",string"` 
} 

Nach meiner Einschätzung des richtigere und nachhaltiger Ansatz ist für Sie eine Client-Bibliothek in so etwas wie JavaScript erstellen und sie in die Registry NPM veröffentlichen, damit andere verwenden, um (privat Repository würde genauso funktionieren). Indem Sie diese Bibliothek bereitstellen, können Sie die API für die Konsumenten sinnvoll gestalten und verhindern, dass Fehler in Ihr Hauptprogramm gelangen.

+1

Dies schlägt auf die umgekehrte Weise fehl, wenn der Client den Wert als Zahl sendet – JimB

+1

@JimB Lesen Sie den Rest meiner Aussage darüber, das Richtige aus technischer Sicht zu tun und eine Client-Bibliothek zu liefern – Woot4Moo

3

Sie können einen benutzerdefinierten Typ mit benutzerdefinierten Unmarshalling-Algorithmus für Ihr "Balance" -Feld verwenden.

Nun gibt es zwei Möglichkeiten:

  • Griff beide Typen:

    package main 
    
    import (
        "encoding/json" 
        "fmt" 
        "strconv" 
    ) 
    
    type Int int 
    
    type account struct { 
        Name string 
        Balance Int 
    } 
    
    func (i *Int) UnmarshalJSON(b []byte) (err error) { 
        var s string 
        err = json.Unmarshal(b, &s) 
        if err == nil { 
         var n int 
         n, err = strconv.Atoi(s) 
         if err != nil { 
          return 
         } 
         *i = Int(n) 
         return 
        } 
    
        var n int 
        err = json.Unmarshal(b, &n) 
        if err == nil { 
         *i = Int(n) 
        } 
        return 
    } 
    
    func main() { 
        for _, in := range [...]string{ 
         `{"Name": "foo", "Balance": 42}`, 
         `{"Name": "foo", "Balance": "111"}`, 
        } { 
         var a account 
         err := json.Unmarshal([]byte(in), &a) 
         if err != nil { 
          fmt.Printf("Error decoding JSON: %v\n", err) 
         } else { 
          fmt.Printf("Decoded OK: %v\n", a) 
         } 
        } 
    } 
    

    Playground link.

  • Handle nur ein numerischer Typ, und alles, was nicht mit einem vernünftigen Fehler:

    package main 
    
    import (
        "encoding/json" 
        "fmt" 
    ) 
    
    type Int int 
    
    type account struct { 
        Name string 
        Balance Int 
    } 
    
    type FormatError struct { 
        Want string 
        Got string 
        Offset int64 
    } 
    
    func (fe *FormatError) Error() string { 
        return fmt.Sprintf("Invalid data format at %d: want: %s, got: %s", 
         fe.Offset, fe.Want, fe.Got) 
    } 
    
    func (i *Int) UnmarshalJSON(b []byte) (err error) { 
        var n int 
        err = json.Unmarshal(b, &n) 
        if err == nil { 
         *i = Int(n) 
         return 
        } 
        if ute, ok := err.(*json.UnmarshalTypeError); ok { 
         err = &FormatError{ 
          Want: "number", 
          Got: ute.Value, 
          Offset: ute.Offset, 
         } 
        } 
        return 
    } 
    
    func main() { 
        for _, in := range [...]string{ 
         `{"Name": "foo", "Balance": 42}`, 
         `{"Name": "foo", "Balance": "111"}`, 
        } { 
         var a account 
         err := json.Unmarshal([]byte(in), &a) 
         if err != nil { 
          fmt.Printf("Error decoding JSON: %#v\n", err) 
          fmt.Printf("Error decoding JSON: %v\n", err) 
         } else { 
          fmt.Printf("Decoded OK: %v\n", a) 
         } 
        } 
    } 
    

    Playground link.

Es gibt eine dritte Möglichkeit: Schreiben Sie benutzerdefinierte Unmarshaler für den ganzen account Typen, aber es erfordert mehr beteiligt Code, weil Sie tatsächlich über die Eingabe JSON-Daten durchlaufen bräuchten, um die Methoden des encoding/json.Decoder Typs.

nach dem Lesen

What's the best way to take that incoming request, in Go, and return the error message, "Balance must be a string", for any arbitrary number of integer fields on a request?

vorsichtiger, ich gebe für die ganze Art, die einen benutzerdefinierten Parser aufweist, ist die einzig sinnvolle Möglichkeit, es sei denn, Sie sind OK mit einem 3rd-party package implementing a parser supporting validation via JSON schema (ich glaube, ich this aussehen würde zunächst als juju ist ein ziemlich etabliertes Produkt).

2

sein Eine Lösung hierfür könnte eine Karte eine Art Behauptung zu verwenden, indem Sie die JSON-Daten in entpacken:

type account struct { 
    Name string 
    Balance int 
} 

var str = `{ 
    "name": "[email protected]", 
    "balance": "3" 
}` 

func main() { 
    var testing = map[string]interface{}{} 
    err := json.Unmarshal([]byte(str), &testing) 
    if err != nil { 
     fmt.Println(err) 
    } 

    val, ok := testing["balance"] 
    if !ok { 
     fmt.Println("missing field balance") 
     return 
    } 

    nv, ok := val.(float64) 
    if !ok { 
     fmt.Println("balance should be a number") 
     return 
    } 

    fmt.Printf("%+v\n", nv) 
} 

hier http://play.golang.org/p/iV7Qa1RrQZ

Die Art Behauptung float64 Verwendung erfolgt, weil es der Standard-Zahlentyp, der von Gos JSON-Decoder unterstützt wird.

Es sollte angemerkt werden, dass diese Verwendung von interface{} ist wahrscheinlich nicht die Mühe wert.

Die UnmarshalTypeError (https://golang.org/pkg/encoding/json/#UnmarshalFieldError) enthält ein Feld Offset, mit dem der Inhalt der JSON-Daten abgerufen werden kann, die den Fehler ausgelöst haben.

Sie könnte zum Beispiel eine Nachricht von der Art zurück:

cannot unmarshal string into Go value of type int near `"balance": "3"` 
Verwandte Themen