encoding/json: add FlexObject for encoding/decoding between JSON and flex Go types.

143 views
Skip to first unread message

Ally Dale

unread,
May 9, 2021, 8:30:59 AM5/9/21
to golang-nuts
reffer issue
## Sometimes we have to deal with JSON that with unknown data types.

```
[
    {
        "kind":"dog",
        "attr":{
            "type":"Collie",
            "color":"black"
        }
    },
    {
        "kind":"duck",
        "attr":{
            "weight":1.2
        }
    }
]

```

The attr field maybe [below Go types](https://github.com/vipally/glab/blob/master/lab27/json_test.go#L27), which is not a certain Go type that is decided on the value of field "kind".

```Go
type DogAttr struct {
Type  string `json:"type"`
Color string `json:"color"`
}

type DuckAttr struct {
Weight float64
}
```


Currently, we may deal with this case as [below](https://github.com/vipally/glab/blob/master/lab27/json_raw_test.go#L9):

```Go
func TestDecodeRaw(t *testing.T) {
var factory = NewFactory()
factory.MustReg("dog", (*DogAttr)(nil))
factory.MustReg("duck", (*DuckAttr)(nil))

type AnimalRaw struct {
Kind string          `json:"kind"`
Attr json.RawMessage `json:"attr"`
}
var animals []AnimalRaw
json.Unmarshal(sampleJson, &animals)
for i, v := range animals {
d, _ := factory.Create(v.Kind)
json.Unmarshal(v.Attr, d)
fmt.Printf("index %d, kind=%s attr=%#v\n", i, v.Kind, d)
}
// Output:
// index 0, kind=dog attr=&lab27.DogAttr{Type:"Collie", Color:"black"}
// index 1, kind=duck attr=&lab27.DuckAttr{Weight:1.2}
}
```

But the way of [generate a sample JSON](https://github.com/vipally/glab/blob/master/lab27/json_raw_test.go#L38) in this case looks ugly:

```Go
func TestEncodeRaw(t *testing.T) {
type AnimalRaw struct {
Kind string          `json:"kind"`
Attr json.RawMessage `json:"attr"`
}
var animals = []AnimalRaw{
AnimalRaw{
Kind: "dog",
Attr: []byte(`{"type": "Collie","color": "white"}`), // ugly
},
AnimalRaw{
Kind: "duck",
Attr: []byte(`{"Weight": 2.34}`), // ugly
},
}
b, _ := json.MarshalIndent(animals, "", "  ")
fmt.Println(string(b))
// Output:
// [
//  {
//    "kind": "dog",
//    "attr": {
//      "type": "Collie",
//      "color": "white"
//    }
//  },
//  {
//    "kind": "duck",
//    "attr": {
//      "Weight": 2.34
//    }
//  }
// ]
}
```

A [eleganter solution](https://github.com/vipally/go/blob/ally_feat_json_flexobj/src/encoding/json/stream.go#L285) of this case maybe as below. Compare with json.RawMessage, FlexObject can delay JSON decoding
from field "Raw" into field "D" and can direct encoding JSON from field "D".

```Go
// FlexObject is an object that can encoding/decoding JSON between flex Go types.
// It implements Marshaler and Unmarshaler and can delay JSON decoding
// from field Raw and can direct encoding from field D.
type FlexObject struct {
Raw []byte      // raw bytes for delay JSON decoding
D   interface{} // flex object for JSON encoding
}

// MarshalJSON encoding field D as JSON.
func (f FlexObject) MarshalJSON() ([]byte, error) {
return Marshal(f.D)
}

// UnmarshalJSON copy data into field Raw.
func (f *FlexObject) UnmarshalJSON(data []byte) error {
f.Raw = append(f.Raw[0:0], data...)
return nil
}
```
Coordinate with the [flex object factory](https://github.com/vipally/glab/blob/master/lab27/json_factory.go#L50). [The way to deal with this case](https://github.com/vipally/glab/blob/master/lab27/json_test.go#L36) maybe as below:

```Go
func TestFlexObjectFactory(t *testing.T) {
var factory = NewFactory()
factory.MustReg("dog", (*DogAttr)(nil))
factory.MustReg("duck", (*DuckAttr)(nil))

type Animal struct {
Kind string          `json:"kind"`
Attr json.FlexObject `json:"attr"`
}
var animals []Animal
json.Unmarshal(sampleJson, &animals)
for i, v := range animals {
factory.UnmarshalJSONForFlexObj(v.Kind, &v.Attr)
fmt.Printf("index %d, kind=%s attr=%#v\n", i, v.Kind, v.Attr.D)
}
// Output:
// index 0, kind=dog attr=&lab27.DogAttr{Type:"Collie", Color:"black"}
// index 1, kind=duck attr=&lab27.DuckAttr{Weight:1.2}
}
```

And the way to [generate the sample JSON](https://github.com/vipally/glab/blob/master/lab27/json_test.go#L56) maybe as below:

```Go
func TestGenerateJsonByFlexObject(t *testing.T) {
type Animal struct {
Kind string          `json:"kind"`
Attr json.FlexObject `json:"attr"`
}
var animals = []Animal{
Animal{
Kind: "dog",
Attr: json.FlexObject{
D: DogAttr{
Type:  "Collie",
Color: "white",
},
},
},
Animal{
Kind: "duck",
Attr: json.FlexObject{
D: DuckAttr{
Weight: 2.34,
},
},
},
}
b, _ := json.MarshalIndent(animals, "", "  ")
fmt.Println(string(b))
// Ooutput:
// [
//  {
//    "kind": "dog",
//    "attr": {
//      "type": "Collie",
//      "color": "white"
//    }
//  },
//  {
//    "kind": "duck",
//    "attr": {
//      "Weight": 2.34
//    }
//  }
// ]
}
```

**As above shows, [json.FlexObject](https://github.com/vipally/go/blob/ally_feat_json_flexobj/src/encoding/json/stream.go#L285) coordinate with [json.Factory](https://github.com/vipally/glab/blob/master/lab27/json_factory.go#L50) makes the dealing with JSON flex object case eleganter and automatically.**
**If the proposal is accepted, it will be my pleasure to push the PR.**

Brian Candler

unread,
May 9, 2021, 3:14:45 PM5/9/21
to golang-nuts
On Sunday, 9 May 2021 at 13:30:59 UTC+1 Ally Dale wrote:
But the way of [generate a sample JSON](https://github.com/vipally/glab/blob/master/lab27/json_raw_test.go#L38) in this case looks ugly:


It doesn't have to be as ugly as that:

Ally Dale

unread,
May 9, 2021, 11:43:35 PM5/9/21
to golang-nuts
see the reffer issue

both json.RawMessage and interface{} cannot solve this problem.
```Go
        // AnimalRaw can only used to delay decoding JSON
type AnimalRaw struct {
Kind string          `json:"kind"`
Attr json.RawMessage `json:"attr"`
}
        
        // Animal  can only used to generate sample JSON
type Animal struct {
Kind string          `json:"kind"`
Attr interface{} `json:"attr"`
}
```

Brian Candler

unread,
May 10, 2021, 3:17:04 AM5/10/21
to golang-nuts
That is true, you need a separate wrapper object for decoding (the one with json.RawMessage).  But the wrapped objects are the same.
Reply all
Reply to author
Forward
0 new messages