diff --git a/patch_test.go b/patch_test.go index 8dd7b3e..023ab9f 100644 --- a/patch_test.go +++ b/patch_test.go @@ -87,6 +87,13 @@ type I struct { I interface{} `json:"i"` } +// we have to create an alias for 'any' so that we can embedd it in to J as an exported type +type EmbeddedAlias any + +type J struct { + EmbeddedAlias `json:"embedded"` +} + var _ = Describe("JSONPatch", func() { Context("CreateJsonPatch_pointer_values", func() { It("pointer", func() { @@ -341,6 +348,43 @@ var _ = Describe("JSONPatch", func() { testPatchWithExpected(H{Ignored: "new", NotIgnored: "new"}, H{Ignored: "old", NotIgnored: "old"}, H{Ignored: "old", NotIgnored: "new"}) }) }) + Context("CreateJsonPatch_embedded", func() { + It("primitive", func() { + // add + testPatch(J{"2"}, J{""}) + // replace + testPatch(J{"2"}, J{"1"}) + // remove // removing an embedded base will remove the whole JSON object + testPatchWithExpected(J{""}, J{"2"}, struct{}{}) + // no change + testPatch(J{}, J{}) + testPatch(J{""}, J{""}) + testPatch(J{"1"}, J{"1"}) + }) + It("slice", func() { + // add + testPatch(J{[]string{"1"}}, J{[]string{}}) + // replace + testPatch(J{[]string{"2"}}, J{[]string{"1"}}) + // remove + testPatch(J{[]string{}}, J{[]string{"2"}}) + // no change + testPatch(J{[]string{}}, J{[]string{}}) + testPatch(J{[]string{""}}, J{[]string{""}}) + testPatch(J{[]string{"1"}}, J{[]string{"1"}}) + }) + It("struct", func() { + // add + testPatch(J{C{Str: "1"}}, J{C{}}) + // replace + testPatch(J{C{Str: "2"}}, J{C{Str: "1"}}) + // remove + testPatch(J{C{}}, J{C{Str: "1"}}) + // no change + testPatch(J{C{}}, J{C{}}) + testPatch(J{C{Str: "2"}}, J{C{Str: "2"}}) + }) + }) Context("CreateJsonPatch_with_predicates", func() { var predicate jsonpatch.Predicate BeforeEach(func() { diff --git a/walker.go b/walker.go index 6da556a..94608b7 100644 --- a/walker.go +++ b/walker.go @@ -264,7 +264,8 @@ func (w *walker) processStruct(modified, current reflect.Value, pointer JSONPoin return nil } - if modified.Type().PkgPath() == "time" && modified.Type().Name() == "Time" { + var modifiedType = modified.Type() + if modifiedType.PkgPath() == "time" && modifiedType.Name() == "Time" { m, err := toTimeStrValue(modified) if err != nil { return err @@ -279,13 +280,27 @@ func (w *walker) processStruct(modified, current reflect.Value, pointer JSONPoin // process all struct fields, the order of the fields of the modified and current JSON object is identical because their types match for j := 0; j < modified.NumField(); j++ { - tag := strings.Split(modified.Type().Field(j).Tag.Get(jsonTag), ",")[0] - if tag == "" || tag == "_" || !modified.Field(j).CanInterface() { + var modifiedField = modified.Field(j) + var modifiedFieldType = modifiedType.Field(j) + + tag := strings.Split(modifiedFieldType.Tag.Get(jsonTag), ",")[0] + + // allow the tag to be missing if the field is anonymous + haveTagOrIsAnonymous := tag != "" || modifiedFieldType.Anonymous + + if !haveTagOrIsAnonymous || tag == "_" || !modifiedFieldType.IsExported() { // struct fields without a JSON tag set or unexported fields are ignored continue } + + var p2 = pointer + // update the pointer unless the tag is empty + if tag != "" { + p2 = pointer.Add(tag) + } + // process the child's value of the modified and current JSON in a next step - if err := w.walk(modified.Field(j), current.Field(j), pointer.Add(tag)); err != nil { + if err := w.walk(modifiedField, current.Field(j), p2); err != nil { return err } }