Skip to content

Commit 1ec8a90

Browse files
committed
Adding support for JSON streams
1 parent 7f9116e commit 1ec8a90

20 files changed

+1410
-418
lines changed

Readme.md

+28
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,34 @@ $ jaydiff --report --show-types --ignore-excess --ignore-values old.json new.jso
159159
- .f: float64 42
160160
```
161161

162+
JSON streams:
163+
164+
```diff
165+
$ jaydiff --stream --json old.json new.json
166+
167+
[
168+
{"foo":"bar"},
169+
[
170+
2,
171+
3,
172+
4,
173+
{
174+
+ "v": "some"
175+
}
176+
],
177+
+ {"some":"thing"}
178+
]
179+
```
180+
181+
Validating JSON stream types:
182+
183+
```diff
184+
$ jaydiff --ignore-excess --ignore-values --stream-validate --report --show-types base.json stream.json
185+
186+
- [1].bar: float64 4.2
187+
+ [1].bar: string !
188+
```
189+
162190
## Ideas
163191

164192
- JayPatch

config.go

+21-7
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,17 @@ type config struct {
1818
Files files `positional-args:"yes" required:"yes"`
1919
Ignore ignorePatterns `long:"ignore" short:"i" description:"paths to ignore (glob)"`
2020
output
21-
IgnoreExcess bool `long:"ignore-excess" description:"ignore excess keys and arrey elements"`
22-
IgnoreValues bool `long:"ignore-values" description:"ignore scalar's values (only type is compared)"`
23-
OutputReport bool `long:"report" short:"r" description:"output report format"`
24-
UseSliceMyers bool `long:"slice-myers" description:"use myers algorithm for slices"`
25-
Version func() `long:"version" short:"v" description:"print release version"`
21+
IgnoreExcess bool `long:"ignore-excess" description:"ignore excess keys and array elements"`
22+
IgnoreValues bool `long:"ignore-values" description:"ignore scalar's values (only type is compared)"`
23+
OutputReport bool `long:"report" short:"r" description:"output report format"`
24+
UseSliceMyers bool `long:"slice-myers" description:"use myers algorithm for slices"`
25+
26+
Stream bool `long:"stream" description:"treat FILE_1 and FILE_2 as JSON streams"`
27+
StreamLines bool `long:"stream-lines" description:"read JSON stream line by line (expecting 1 JSON value per line)"`
28+
StreamIgnoreExcess bool `long:"stream-ignore-excess" description:"ignore excess values in JSON stream"`
29+
StreamValidate bool `long:"stream-validate" description:"compare FILE_2 JSON stream against FILE_1 single value"`
30+
31+
Version func() `long:"version" short:"v" description:"print release version"`
2632
}
2733

2834
type output struct {
@@ -53,16 +59,24 @@ func readConfig() config {
5359
fmt.Fprintf(os.Stderr, "Incompatible options --json and --show-types\n")
5460
os.Exit(statusUsage)
5561
}
62+
63+
c.InferFlags()
64+
65+
return c
66+
}
67+
68+
func (c *config) InferFlags() {
5669
if c.JSON {
5770
c.JSONValues = true
5871
}
5972
if c.JSON && c.OutputReport {
6073
c.JSON = false
6174
}
75+
if c.StreamLines || c.StreamValidate {
76+
c.Stream = true
77+
}
6278

6379
c.output.Colorized = terminal.IsTerminal(int(os.Stdout.Fd()))
64-
65-
return c
6680
}
6781

6882
func (c config) Opts() []diff.ConfigOpt {

diff/diff.go

+41-16
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ type diffFn func(c config, lhs, rhs interface{}, visited *visited) (Differ, erro
3131

3232
// Diff generates a tree representing differences and similarities between two objects.
3333
//
34-
// Diff supports maps, slices and scalars (comparables types such as int, string, etc ...).
34+
// Diff supports maps, slices, Stream and scalars (comparables types such as int, string, etc ...).
3535
// When an unsupported type is encountered, an ErrUnsupported error is returned.
3636
func Diff(lhs, rhs interface{}, opts ...ConfigOpt) (Differ, error) {
3737
c := defaultConfig()
@@ -56,26 +56,38 @@ func diff(c config, lhs, rhs interface{}, visited *visited) (Differ, error) {
5656
return types{lhs, rhs}, ErrCyclic
5757
}
5858

59-
if valueIsScalar(lhsVal) && valueIsScalar(rhsVal) {
60-
return scalar{lhs, rhs}, nil
59+
return diffValues(c, lhsVal, rhsVal, visited)
60+
}
61+
62+
func diffValues(c config, lhs, rhs reflect.Value, visited *visited) (Differ, error) {
63+
if valueIsStream(lhs) && valueIsStream(rhs) {
64+
return newStream(c, lhs.Interface(), rhs.Interface(), visited)
65+
}
66+
67+
if valueIsScalar(lhs) && valueIsScalar(rhs) {
68+
return scalar{lhs.Interface(), rhs.Interface()}, nil
6169
}
62-
if lhsVal.Kind() != rhsVal.Kind() {
63-
return types{lhs, rhs}, nil
70+
if lhs.Kind() != rhs.Kind() {
71+
return types{lhs.Interface(), rhs.Interface()}, nil
6472
}
6573

66-
switch lhsVal.Kind() {
74+
switch lhs.Kind() {
6775
case reflect.Slice, reflect.Array:
68-
return c.sliceFn(c, lhs, rhs, visited)
76+
return c.sliceFn(c, lhs.Interface(), rhs.Interface(), visited)
6977
case reflect.Map:
70-
return newMap(c, lhs, rhs, visited)
78+
return newMap(c, lhs.Interface(), rhs.Interface(), visited)
7179
case reflect.Struct:
72-
return newStruct(c, lhs, rhs, visited)
80+
return newStruct(c, lhs.Interface(), rhs.Interface(), visited)
7381
}
7482

75-
return types{lhs, rhs}, &ErrUnsupported{lhsVal.Type(), rhsVal.Type()}
83+
return types{lhs.Interface(), rhs.Interface()}, &ErrUnsupported{lhs.Type(), rhs.Type()}
7684
}
7785

7886
func indirectValueOf(i interface{}) (reflect.Value, interface{}) {
87+
if _, ok := i.(Stream); ok {
88+
return reflect.ValueOf(i), i
89+
}
90+
7991
v := reflect.Indirect(reflect.ValueOf(i))
8092
if !v.IsValid() || !v.CanInterface() {
8193
return reflect.ValueOf(i), i
@@ -84,6 +96,16 @@ func indirectValueOf(i interface{}) (reflect.Value, interface{}) {
8496
return v, v.Interface()
8597
}
8698

99+
func valueIsStream(v reflect.Value) bool {
100+
if !v.IsValid() || !v.CanInterface() {
101+
return false
102+
}
103+
104+
_, ok := v.Interface().(Stream)
105+
106+
return ok
107+
}
108+
87109
func valueIsScalar(v reflect.Value) bool {
88110
switch v.Kind() {
89111
default:
@@ -122,9 +144,7 @@ func IsExcess(d Differ) bool {
122144
switch d.(type) {
123145
default:
124146
return false
125-
case mapExcess:
126-
return true
127-
case sliceExcess:
147+
case mapExcess, sliceExcess, streamExcess:
128148
return true
129149
}
130150
}
@@ -134,9 +154,7 @@ func IsMissing(d Differ) bool {
134154
switch d.(type) {
135155
default:
136156
return false
137-
case mapMissing:
138-
return true
139-
case sliceMissing:
157+
case mapMissing, sliceMissing, streamMissing:
140158
return true
141159
}
142160
}
@@ -176,6 +194,13 @@ func IsSlice(d Differ) bool {
176194
return ok
177195
}
178196

197+
// IsStream returns true if d is a diff between towo slices
198+
func IsStream(d Differ) bool {
199+
_, ok := d.(stream)
200+
201+
return ok
202+
}
203+
179204
type lhsGetter interface {
180205
LHS() interface{}
181206
}

0 commit comments

Comments
 (0)