@@ -13,7 +13,7 @@ type CompareResults struct {
1313 MismatchedFields []FieldMismatch
1414
1515 // MissingFields is a list of JSON field names which were not in src.
16- MissingFields []string
16+ MissingFields []FieldMissing
1717}
1818
1919// Errors returns a MismatchError containing the type errors. If there were no
@@ -32,6 +32,20 @@ func (cr *CompareResults) Errors() error {
3232 return MismatchError (m )
3333}
3434
35+ type FieldMissing struct {
36+ // Field is the JSON name of the field.
37+ Field string
38+
39+ // Path is the full path to the field.
40+ Path []string
41+ }
42+
43+ // String returns the field name with its path.
44+ // e.g: "Cat.Foo"
45+ func (f FieldMissing ) String () string {
46+ return FieldNameWithPath (f .Field , f .Path )
47+ }
48+
3549// FieldMismatch represents a type mismatch between a struct field and a map field.
3650type FieldMismatch struct {
3751 // Field is the JSON name of the field.
@@ -42,6 +56,9 @@ type FieldMismatch struct {
4256
4357 // Actual is the actual type (type of the src field).
4458 Actual string
59+
60+ // Path is the full path to the field.
61+ Path []string
4562}
4663
4764// Message returns the field mismatch error as a string.
@@ -55,12 +72,12 @@ func (f FieldMismatch) Message() string {
5572}
5673
5774// Message returns the field mismatch error as a string, and includes the field name
58- // in the message.
59- // e.g: "expected Foo to be an int but it's a string"
75+ // with its path in the message.
76+ // e.g: "expected Cat. Foo to be an int but it's a string"
6077func (f FieldMismatch ) MessageWithField () string {
6178 return fmt .Sprintf (
6279 `expected "%s" to be %s but it's %s` ,
63- f .Field ,
80+ FieldNameWithPath ( f .Field , f . Path ) ,
6481 TypeNameWithArticle (f .Expected ),
6582 TypeNameWithArticle (f .Actual ),
6683 )
@@ -146,7 +163,7 @@ func CompareMapToStruct(dst interface{}, src map[string]interface{}, opts *Compa
146163
147164 results := & CompareResults {
148165 MismatchedFields : []FieldMismatch {},
149- MissingFields : []string {},
166+ MissingFields : []FieldMissing {},
150167 }
151168
152169 compare (v .Elem ().Type (), src , opts , results )
@@ -160,6 +177,7 @@ func CompareMapToStruct(dst interface{}, src map[string]interface{}, opts *Compa
160177// t points to.
161178func DefaultCanConvert (t reflect.Type , v reflect.Value ) bool {
162179 isPtr := t .Kind () == reflect .Ptr
180+ isStruct := t .Kind () == reflect .Struct
163181 dstType := t
164182
165183 // Check if v is a nil value.
@@ -170,6 +188,12 @@ func DefaultCanConvert(t reflect.Type, v reflect.Value) bool {
170188 // If the dst is a pointer, check if we can convert to the type it's pointing to.
171189 if isPtr {
172190 dstType = t .Elem ()
191+ isStruct = t .Elem ().Kind () == reflect .Struct
192+ }
193+
194+ // If the dst is a struct, we should check its nested fields.
195+ if isStruct {
196+ return v .Kind () == reflect .Map
173197 }
174198
175199 if ! v .Type ().ConvertibleTo (dstType ) {
@@ -212,6 +236,9 @@ func compare(t reflect.Type, src map[string]interface{}, opts *CompareOpts, resu
212236 continue
213237 }
214238
239+ // If the field is a nested struct also check its fields.
240+ shouldCheckNested := isStructType (f .Type )
241+
215242 if srcField , ok := src [fieldName ]; ok {
216243 srcValue := reflect .ValueOf (srcField )
217244
@@ -231,9 +258,47 @@ func compare(t reflect.Type, src map[string]interface{}, opts *CompareOpts, resu
231258 }
232259
233260 results .MismatchedFields = append (results .MismatchedFields , mismatch )
261+ // There is no point to check nested fields if their parent is mismatched.
262+ shouldCheckNested = false
234263 }
235264 } else {
236- results .MissingFields = append (results .MissingFields , fieldName )
265+ missing := FieldMissing {Field : fieldName }
266+
267+ results .MissingFields = append (results .MissingFields , missing )
268+ // There is no point to check nested fields if their parent is missing.
269+ shouldCheckNested = false
270+ }
271+
272+ if shouldCheckNested {
273+ nested := src [fieldName ].(map [string ]interface {})
274+ nestedType := f .Type
275+ if f .Type .Kind () == reflect .Ptr {
276+ nestedType = nestedType .Elem ()
277+ }
278+
279+ checkNestedFields (nestedType , fieldName , nested , opts , results )
280+ }
281+ }
282+ }
283+
284+ func checkNestedFields (t reflect.Type , fieldName string , src map [string ]interface {}, opts * CompareOpts , results * CompareResults ) {
285+ // Remember count of fields to check if new errors occured.
286+ mismatchCount := len (results .MismatchedFields )
287+ missingCount := len (results .MissingFields )
288+
289+ compare (t , src , opts , results )
290+
291+ // If there were new mismatched fields, add the current field name to their path.
292+ if mismatchCount != len (results .MismatchedFields ) {
293+ for mi := mismatchCount ; mi < len (results .MismatchedFields ); mi ++ {
294+ results .MismatchedFields [mi ].Path = append ([]string {fieldName }, results .MismatchedFields [mi ].Path ... )
295+ }
296+ }
297+
298+ // If there were new missing fields, add the current field name to their path.
299+ if missingCount != len (results .MissingFields ) {
300+ for mi := missingCount ; mi < len (results .MissingFields ); mi ++ {
301+ results .MissingFields [mi ].Path = append ([]string {fieldName }, results .MissingFields [mi ].Path ... )
237302 }
238303 }
239304}
@@ -262,6 +327,17 @@ func isIntegerType(t reflect.Type) (yes bool, unsigned bool) {
262327 return
263328}
264329
330+ // isStructType returns whether the type is a struct or a pointer to it.
331+ func isStructType (t reflect.Type ) (yes bool ) {
332+ switch t .Kind () {
333+ case reflect .Struct :
334+ yes = true
335+ case reflect .Ptr :
336+ yes = t .Elem ().Kind () == reflect .Struct
337+ }
338+ return
339+ }
340+
265341// parseField returns the field's JSON name.
266342func parseField (f reflect.StructField ) (name string , ignore bool ) {
267343 tag := f .Tag .Get ("json" )
0 commit comments