JSON und Umgang mit nicht exportierten Feldern

73

Gibt es einen technischen Grund, warum nicht exportierte Felder von encoding / json nicht berücksichtigt werden? Wenn nicht und es ist eine willkürliche Entscheidung, könnte es eine zusätzliche Hintertüroption (sagen wir '+') geben, die aufgenommen werden kann, obwohl sie nicht exportiert wird?

Das Erfordernis des Client-Codes zum Exportieren, um diese Funktionalität zu erhalten, ist unglücklich, insbesondere wenn Kleinbuchstaben die Kapselung bereitstellen oder die Entscheidung, Strukturen zu marshallen, viel später als das Design dieser Strukturen erfolgt.

Wie gehen die Leute damit um? Einfach alles exportieren?

Wenn Sie keine Feldnamen exportieren, ist es auch schwierig, den vorgeschlagenen Redewendungen zu folgen. Ich denke, wenn eine Struktur X Feld Y hat, können Sie keine Zugriffsmethode Y () haben. Wenn Sie den Schnittstellenzugriff auf Y bereitstellen möchten, müssen Sie sich einen neuen Namen für den Getter einfallen lassen, und unabhängig davon, was Sie laut http://golang.org/doc/effective_go.html#Getters für etwas Unidiomatisches erhalten

user1338952
quelle

Antworten:

101

Es gibt einen technischen Grund. Die JSON-Bibliothek kann Felder nur mit Reflect anzeigen, wenn sie exportiert werden. Ein Paket kann nur die nicht exportierten Typenfelder in seinem eigenen Paket anzeigen

Um Ihr Problem zu lösen, können Sie einen nicht exportierten Typ mit exportierten Feldern erstellen. Json wird die Zuordnung zu einem nicht exportierten Typ aufheben, wenn er problemlos an ihn übergeben wird, aber nicht in den API-Dokumenten angezeigt wird. Sie können dann einen exportierten Typ erstellen, der den nicht exportierten Typ einbettet. Dieser exportierte Typ würde dann Methoden zum Implementieren der json.Marshalerund json.Unmarshaler-Schnittstellen benötigen .

Hinweis: Der gesamte Code ist nicht getestet und wird möglicherweise nicht einmal kompiliert.

type jsonData struct {
    Field1 string
    Field2 string
}

type JsonData struct {
    jsonData
}

// Implement json.Unmarshaller
func (d *JsonData) UnmarshalJSON(b []byte) error {
    return json.Unmarshal(b, &d.jsonData)
}

// Getter
func (d *JsonData) Field1() string {
    return d.jsonData.Field1
}
Stephen Weinberg
quelle
Für die Aufzeichnung musste ich Unmarshall mit "json.Unmarshal (b, & d.jsonData)" entfernen. Habe ich etwas falsch gemacht oder wird das erwartet?
Derek
@Derek, danke, ich habe meine Antwort aktualisiert. Wie gesagt der Code war ungetestet. Ich habe anscheinend auch eine return-Anweisung in meiner UnmarshalJSON()Methode vergessen . Ich habe das auch behoben.
Stephen Weinberg
5
Ich bin ein bisschen spät , um die Show, aber ... während die oben genannten Arbeiten, Field1 und Field2 werden exportiert. Sie können Field1 und Field2 von JsonData (mit einem Großbuchstaben J) außerhalb dieses Pakets lesen und schreiben. Obwohl dies theoretisch cool ist, macht es eigentlich nichts anderes als den Export sowohl des Typs als auch der Felder.
Davidjosepha
1
@davidjosepha Sie haben Recht, da die Antwort ursprünglich angezeigt wurde, obwohl jemand in Ihren Quellcode schauen müsste, um dies zu wissen, Field1und Field2anwesend sind, da Godoc die nicht exportierte Struktur nicht auflisten würde jsonData. Dieses Problem kann jedoch behoben werden, indem in der JsonData-Struktur kein anonymes Feld verwendet wird und ein nicht exportiertes benanntes Feld vorhanden ist. Dies erfordert dann , dass Field1und Field2werden durch den Namen, unexported Feld abgerufen. Ich habe die Antwort bearbeitet, um diese Methode zu verwenden.
Darkwing
64

Stephens Antwort ist vollständig. Wenn Sie in Ihrem JSON nur Kleinbuchstaben benötigen, können Sie den Schlüsselnamen wie folgt manuell angeben:

type Whatever struct {
    SomeField int `json:"some_field"`
}

Auf diese Weise erzeugt das Marshalling eines Whatever den Schlüssel "some_field" für das Feld SomeField (anstatt "SomeField" in Ihrem json zu haben).

Wenn Sie nicht exportierte Felder behalten möchten, können Sie auch die Schnittstelle json.Marshaler implementieren, indem Sie eine Methode mit der Signatur definieren MarshalJSON() ([]byte, error). Eine Möglichkeit, dies zu tun, besteht darin, ein Strukturliteral zu verwenden, das einfach Versionen der nicht exportierten Felder exportiert hat, wie folgt:

type Whatever struct {
    someField int
}

func (w Whatever) MarshalJSON() ([]byte, error) {
    return json.Marshal(struct{
        SomeField int `json:"some_field"`
    }{
        SomeField: w.someField,
    })
}

Das kann etwas umständlich sein, daher können Sie auch a verwenden, map[string]interface{}wenn Sie es vorziehen:

func (w Whatever) MarshalJSON() ([]byte, error) {
    return json.Marshal(map[string]interface{}{
        "some_field": w.SomeField,
    })
}

Es sollte jedoch beachtet werden, dass das Marshalling interface{}einige Einschränkungen aufweist und Dinge wie das Marschallieren uint64eines Schwimmers bewirken kann, was zu einem Präzisionsverlust führt. (alles Code ungetestet)

Jorelli
quelle
Technisch gesehen sind ALLE Zahlen in Javascript Floats.
Tyler Eaves
23
JSON ist kein JavaScript. Die JSON-Spezifikation gibt lediglich an, welche Zeichen akzeptabel sind, nicht welche numerischen Bereiche gültig sind. Eine Nummer wie 876234958273645982736459827346598237465923847561203947812435968234659827346 ist in JSON weiterhin gültig, auch wenn sie von JavaScript nicht verstanden werden kann. In einem Beispiel aus der Praxis stellt die Twitter-API Tweet-IDs als 64-Bit-Ganzzahlen ohne Vorzeichen dar, was in JavaScript nicht gültig ist, aber in JSON gültig ist.
Jorelli
1
Derzeit ist alles wahr, aber der Name ist immer noch JSON, was historisch für "JavaScript Object Notation" steht. Einer dieser winzigen überraschendsten bits , dass Sie es zu NJSON (NewJSON oder eher NotJavaScript) umbenennen wollen :)
quetzalcoatl
0

Die Verwendung einer Schnittstelle wäre eine weitere Option.

type Person interface {
    Name() string
    SetName(name string) Person
    Age() int
    SetAge(age int) Person
}

type person struct {
    Name_ string
    Age_  int
}

func (p *person) Name() string {
    return p.Name_
}

func (p *person) SetName(name string) Person {
    p.Name_ = name

    return p
}

func (p *person) Age() int {
    return p.Age_
}

func (p *person) SetAge(age int) Person {
    p.Age_ = age

    return p
}

func NewPerson() Person {
    return &person{}
}

Da person structes sich um Kleinbuchstaben handelt, können Sie außerhalb des Pakets nicht auf die öffentlichen Felder zugreifen. Um einen Personenwert zu instanziieren, geben Sie einen Konstruktor an, der ihn in Großbuchstaben zurückgibt Person.

Spielplatz

Steven Eckhoff
quelle