diff --git a/diff.go b/diff.go new file mode 100644 index 0000000..b2a9ccd --- /dev/null +++ b/diff.go @@ -0,0 +1,81 @@ +package jsonpointer + +import ( + "log" + "reflect" +) + +// DiffType represents the type of difference between two JSON +// structures. +type DiffType int + +const ( + // MissingA designates a path that was missing from the first + // argument of the diff. + MissingA = DiffType(iota) + // MissingB designates a path taht was missing from the second + // argument of the diff. + MissingB + // Different designates a path that is found in both + // arguments, but with different values. + Different +) + +var diffNames = []string{"missing a", "missing b", "different"} + +func (d DiffType) String() string { + return diffNames[d] +} + +func must(err error) { + if err != nil { + log.Panic(err) + } +} + +// Diff returns the differences between two json blobs. +func Diff(a, b []byte) (map[string]DiffType, error) { + alist, err := ListPointers(a) + if err != nil { + return nil, err + } + blist, err := ListPointers(b) + if err != nil { + return nil, err + } + + amap := map[string]bool{} + bmap := map[string]bool{} + for _, v := range alist { + amap[v] = true + } + for _, v := range blist { + bmap[v] = true + } + + rv := map[string]DiffType{} + + for _, v := range alist { + if v == "" { + continue + } + if bmap[v] { + var aval, bval interface{} + must(FindDecode(a, v, &aval)) + must(FindDecode(b, v, &bval)) + if !reflect.DeepEqual(aval, bval) { + rv[v] = Different + } + } else { + rv[v] = MissingB + } + } + + for _, v := range blist { + if !amap[v] { + rv[v] = MissingA + } + } + + return rv, nil +} diff --git a/diff_test.go b/diff_test.go new file mode 100644 index 0000000..46e0b54 --- /dev/null +++ b/diff_test.go @@ -0,0 +1,95 @@ +package jsonpointer + +import ( + "io" + "reflect" + "testing" +) + +func TestDiffNil(t *testing.T) { + diffs, err := Diff(nil, nil) + if err != nil { + t.Fatalf("Expected no error on nil diff, got %v", err) + } + if len(diffs) != 0 { + t.Errorf("Expected no diffs, got %v", diffs) + } +} + +func TestDiffNames(t *testing.T) { + tests := map[DiffType]string{ + MissingA: "missing a", + MissingB: "missing b", + Different: "different", + } + + for k, v := range tests { + if k.String() != v { + t.Errorf("Expected %v for %d, got %v", v, k, k) + } + } +} + +func TestMust(t *testing.T) { + must(nil) // no panic + panicked := false + func() { + defer func() { panicked = recover() != nil }() + must(io.EOF) + }() + if !panicked { + t.Fatalf("Expected a panic, but didn't get one") + } +} + +func TestDiff(t *testing.T) { + var ( + aFirst = `{"a": 1, "b": 3.2}` + bFirst = `{"b":3.2,"a":1}` + aTwo = `{"a": 2, "b": 3.2}` + aOnly1 = `{"a": 1}` + aOnly3 = `{"a": 3}` + broken = `{x}` + ) + + tests := []struct { + name string + a, b string + exp map[string]DiffType + errored bool + }{ + {"Empty", "", "", map[string]DiffType{}, false}, + {"Identity", aFirst, aFirst, map[string]DiffType{}, false}, + {"Same", aFirst, bFirst, map[string]DiffType{}, false}, + {"Other order", aFirst, bFirst, map[string]DiffType{}, false}, + {"A diff", aFirst, aTwo, map[string]DiffType{"/a": Different}, false}, + {"A diff rev", aTwo, aFirst, map[string]DiffType{"/a": Different}, false}, + {"Missing b <- 1", aFirst, aOnly1, map[string]DiffType{"/b": MissingB}, false}, + {"Missing b -> 1", aOnly1, aFirst, map[string]DiffType{"/b": MissingA}, false}, + {"Missing b <- 3", aTwo, aOnly3, map[string]DiffType{ + "/a": Different, + "/b": MissingB, + }, false}, + {"Missing b -> 3", aOnly3, aTwo, map[string]DiffType{ + "/a": Different, + "/b": MissingA, + }, false}, + {"Broken A", broken, aFirst, nil, true}, + {"Broken B", aFirst, broken, nil, true}, + } + + for _, test := range tests { + diffs, err := Diff([]byte(test.a), []byte(test.b)) + if (err != nil) != test.errored { + t.Errorf("Expected error=%v on %q: %v", test.errored, test.name, err) + } + if err != nil { + continue + } + if !reflect.DeepEqual(test.exp, diffs) { + t.Errorf("Unexpected diff for %q: %v\nwanted %v", + test.name, diffs, test.exp) + } + } + +}