3 ms·
so, just for fun, here's a way to do that check without double unmarshalling and allocating maps and such for everything under your struct (also does a check fo
by Vendan 8y ago
so, just for fun, here's a way to do that check without double unmarshalling and allocating maps and such for everything under your struct (also does a check for extra fields, but you could pull that out if you want):
type Post struct {
Title string `json:"title"`
}
func (v *Post) UnmarshalJSON(b []byte) error {
dec := json.NewDecoder(bytes.NewReader(b))
required := map[string]struct{}{
"title": struct{}{},
}
tok, err := dec.Token()
if err != nil {
return err
}
if d, ok := tok.(json.Delim); !ok || d != '{' {
return errors.New("Expected object")
}
for {
tok, err := dec.Token()
if err != nil {
return err
}
if d, ok := tok.(json.Delim); ok && d == '}' {
break
}
switch tok {
case "title":
delete(required, "title")
err := dec.Decode(&v.Title)
if err != nil {
return err
}
default:
(*v) = Post{}
return errors.New(fmt.Sprintf("Unexpected field %s", tok))
}
}
if len(required) > 0 {
(*v) = Post{}
return errors.New(fmt.Sprintf("Missing %v required fields", len(required)))
}
return nil
}
- atombender 8y agoThanks, that's neat! The challenge here is that I also need to validate values (e.g. support minimum/maximum), not just within structs, but also standalone values, which means an UnmarshalJSON directly on the type. I might end up doing something like your example, though. (Rejecting extra fields is on my list!)
- Vendan 8y agothe json.NewDecoder stuff does still trigger unmarshals of the types in the struct and such: i.e. package main import ( "bytes" "encoding/json" "errors" "fmt" ) func main() { fmt.Println("Hello, playground") var p Post err := json.Unmarshal([]byte(`{"title": "test"}`), &p) fmt.Println(err, p) err = json.Unmarshal([]byte(`{"title2": "test"}`), &p) fmt.Println(err, p) err = json.Unmarshal([]byte(`{}`), &p) fmt.Println(err, p) err = json.Unmarshal([]byte(`{"title": "long test"}`), &p) fmt.Println(err, p) } type Post struct { Title ShortTitle `json:"title"` } func (v *Post) UnmarshalJSON(b []byte) error { dec := json.NewDecoder(bytes.NewReader(b)) required := map[string]struct{}{ "title": struct{}{}, } tok, err := dec.Token() if err != nil { return err } if d, ok := tok.(json.Delim); !ok || d != '{' { return errors.New("Expected object") } for { tok, err := dec.Token() if err != nil { return err } if d, ok := tok.(json.Delim); ok && d == '}' { break } switch tok { case "title": delete(required, "title") err := dec.Decode(&v.Title) if err != nil { return err } default: (*v) = Post{} return errors.New(fmt.Sprintf("Unexpected field %s", tok)) } } if len(required) > 0 { (*v) = Post{} return errors.New(fmt.Sprintf("Missing %v required fields", len(required))) } return nil } type ShortTitle string func (st *ShortTitle) UnmarshalJSON(b []byte) error { var tmpst string err := json.Unmarshal(b, &tmpst) if err != nil { return err } if len(tmpst) > 6 { return errors.New("Title too long!") } *st = ShortTitle(tmpst) return nil }