是否有一种简单的方法可以使用json.Unmarshal(jsonData,& myStruct)来检查myStruct的每个字段是否都已映射。
我可以成像的唯一方法是将结构的每个字段定义为指针,否则您将始终返回初始化的结构。 所以每个作为对象的jsonString(即使是空的{}}都将返回一个初始化的结构,你无法判断json是否代表了你的结构。
我能想到的唯一解决方案是非常不舒服:
package main
import (
"encoding/json"
"fmt"
)
type Person struct {
Name *string `json:name`
Age *int `json:age`
Male *bool `json:male`
}
func main() {
var p *Person
err := json.Unmarshal([]byte("{}"), &p)
// handle parse error
if err != nil {
return
}
// handle json did not match error
if p.Name == nil || p.Age == nil || p.Male == nil {
return
}
// now use the fields with dereferencing and hope you did not forget a nil check
fmt.Println("Hello " + *p.Name)
}
也许可以使用像govalidator这样的库并使用SetFieldsRequiredByDefault。但是你仍然需要执行验证,但仍然需要将整个指针解除引用以进行值检索和nil指针的风险。
我想要的是一个函数,如果字段不匹配,则返回我的unmarshaled json作为结构或错误。 golang json库提供的唯一选择是在未知字段上失败但在丢失字段时失败。
有什么想法吗?
答案 0 :(得分:2)
另一种方法是实现自己的json.Unmarshaler使用反射(类似于默认的json unmarshaler):
有几点需要考虑:
json.Decoder.DisallowUnknownFields
函数无法按预期的方式使用您的类型。您需要自己实现(参见下面的示例)这是这种方法的完全可执行测试:
package sandbox
import (
"encoding/json"
"errors"
"reflect"
"strings"
"testing"
)
type Person struct {
Name string
City string
}
func (p *Person) UnmarshalJSON(data []byte) error {
var m map[string]interface{}
err := json.Unmarshal(data, &m)
if err != nil {
return err
}
v := reflect.ValueOf(p).Elem()
t := v.Type()
var missing []string
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
val, ok := m[field.Name]
delete(m, field.Name)
if !ok {
missing = append(missing, field.Name)
continue
}
switch field.Type.Kind() {
// TODO: if the field is an integer you need to transform the val from float
default:
v.Field(i).Set(reflect.ValueOf(val))
}
}
if len(missing) > 0 {
return errors.New("missing fields: " + strings.Join(missing, ", "))
}
if len(m) > 0 {
extra := make([]string, 0, len(m))
for field := range m {
extra = append(extra, field)
}
// TODO: consider sorting the output to get deterministic errors:
// sort.Strings(extra)
return errors.New("unknown fields: " + strings.Join(extra, ", "))
}
return nil
}
func TestJSONDecoder(t *testing.T) {
cases := map[string]struct {
in string
err string
expected Person
}{
"Empty object": {
in: `{}`,
err: "missing fields: Name, City",
expected: Person{},
},
"Name missing": {
in: `{"City": "Berlin"}`,
err: "missing fields: Name",
expected: Person{City: "Berlin"},
},
"Age missing": {
in: `{"Name": "Friedrich"}`,
err: "missing fields: City",
expected: Person{Name: "Friedrich"},
},
"Unknown field": {
in: `{"Name": "Friedrich", "City": "Berlin", "Test": true}`,
err: "unknown fields: Test",
expected: Person{Name: "Friedrich", City: "Berlin"},
},
"OK": {
in: `{"Name": "Friedrich", "City": "Berlin"}`,
expected: Person{Name: "Friedrich", City: "Berlin"},
},
}
for name, c := range cases {
t.Run(name, func(t *testing.T) {
var actual Person
r := strings.NewReader(c.in)
err := json.NewDecoder(r).Decode(&actual)
switch {
case err != nil && c.err == "":
t.Errorf("Expected no error but go %v", err)
case err == nil && c.err != "":
t.Errorf("Did not return expected error %v", c.err)
case err != nil && err.Error() != c.err:
t.Errorf("Expected error %q but got %v", c.err, err)
}
if !reflect.DeepEqual(c.expected, actual) {
t.Errorf("\nWant: %+v\nGot: %+v", c.expected, actual)
}
})
}
}
答案 1 :(得分:1)
您可以将p
与空结构进行比较,而不是将每个字段与nil
进行比较。
// handle json did not match error
if p == Person{} {
return
}
由于Person{}
将使用每个字段的0值进行初始化,这将导致pointers
的每个属性为nil
,strings
将为{{1} },""
将为ints
,依此类推。