Open cxjava opened 1 year ago
Hello, I am very new to Go and I just started to read and learn from documentation and OSS. I apologize in advance if I come up with silly misconceptions.
In another library, I read about how they deal with arbitrary JSON content i.e.
But what if you don’t know the structure of your JSON data beforehand?
The suggestion was the use of empty interface interface{}
i.e.
map[string]interface{}
[]interface{}
If it isn't supported yet with this, it may be a good generic structure.
https://go.dev/blog/json in section Generic JSON with interface.
Hey! What you propose goes against best practice of single responsibility for a unit of logic. The unmarshal method must carry the single responsibility of deserializing bytes into a Go object only. On that basis, IMO the request shall be discarded.
Provided example indicates that the input data structures have different format, ergo different Go type. Decision about the type shall be taken after deserialization as part of your business logic.
Possible solution for your problem is a custom unmarshall for the destination interface
. Find the execution of the example bellow.
package demo
import (
"fmt"
"reflect"
"testing"
"github.com/goccy/go-json"
)
// your original struct
type obj struct {
Name string `json:"name"`
Address []string `json:"address"`
}
// extended original struct
// custom unmarshal logic was added
type objExt struct {
Name string `json:"name"`
Address []string `json:"address"`
}
func (o *objExt) UnmarshalJSON(data []byte) error {
var tmp map[string]interface{}
err := json.Unmarshal(data, &tmp)
if err != nil {
return err
}
address, ok := tmp["address"]
if !ok {
return fmt.Errorf("address field not found")
}
switch address.(type) {
case []interface{}:
for _, el := range address.([]interface{}) {
o.Address = append(o.Address, el.(string))
}
case string:
o.Address = []string{address.(string)}
default:
o.Address = nil
}
name, ok := tmp["name"]
if !ok {
return fmt.Errorf("name field not found")
}
o.Name = name.(string)
return nil
}
func Test_demo(t *testing.T) {
type args struct {
data []byte
v interface{}
}
tests := []struct {
name string
args args
want interface{}
wantErr bool
}{
{
name: "happy path: array input for []string Go type, original Go struct",
args: args{
data: []byte(`{"name":"xiao","address":["road1","road2"]}`),
v: &obj{},
},
want: &obj{
Name: "xiao",
Address: []string{"road1", "road2"},
},
wantErr: false,
},
{
name: "unhappy path: string input for []string Go type, original Go struct",
args: args{
data: []byte(`{"name":"xiao","address":"road1"}`),
v: &obj{},
},
want: &obj{
Name: "xiao",
},
wantErr: true,
},
{
name: "happy path: array input for []string Go type, extended Go struct",
args: args{
data: []byte(`{"name":"xiao","address":["road1","road2"]}`),
v: &objExt{},
},
want: &objExt{
Name: "xiao",
Address: []string{"road1", "road2"},
},
wantErr: false,
},
{
name: "happy path: string input for []string Go type, extended Go struct",
args: args{
data: []byte(`{"name":"xiao","address":"road1"}`),
v: &objExt{},
},
want: &objExt{
Name: "xiao",
Address: []string{"road1"},
},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(
tt.name, func(t *testing.T) {
if err := json.Unmarshal(tt.args.data, tt.args.v); (err != nil) != tt.wantErr {
t.Errorf("unmarshal() error = %v, wantErr %v", err, tt.wantErr)
}
if !reflect.DeepEqual(tt.want, tt.args.v) {
t.Errorf("unmarshal() got = %v, want %v", tt.args.v, tt.want)
}
},
)
}
}
Hi, I want to handle the json format
"address":["road1","road2"]
and"address":"road1"
using one structAddress []string
. In Java, some library have this ability to handle this special case, like https://stackoverflow.com/questions/17003823/make-jackson-interpret-single-json-object-as-array-with-one-elementCan we add this feature? or do we already have this feature? I am newbie of this lib.
JSON format A:
{"name":"xiao","address":["road1","road2"]}
JSON format B:{"name":"xiao","address":"road1"}
struct:
Test code: