25

Short Story: How can I compare two chunks of JSON? The code below errors out.

var j, j2 interface{}
b := []byte(srv.req)
if err := json.Unmarshal(b, j); err !=nil{
    t.Errorf("err %v, req %s", err, b)
    return
}
d := json.NewDecoder(r.Body)
if err := d.Decode(j2); err !=nil{
    t.Error(err)
    return
}
if !reflect.DeepEqual(j2, j){
    t.Errorf("j %v, j2 %v", j, j2)
    return
}

Long Story: I'm doing some E2E testings and part of this I need to compare the requested JSON body with the received JSON. To do this I've tried to unmarshal the expected and received json to an empty interface (to avoid any type mistakes) but I get an error: json: Unmarshal(nil). I guess encoding/json doesn't like the empty interface so the question is how can I compare two chunks of JSON? A string comparison would be error prone so I'm trying to avoid that.

Anthony Hunt
  • 1,470
  • 5
  • 20
  • 32
  • 1
    I *think* you just need to change your declaration to `j, j2 := map[string]interface{}{}, map[string]interface{}{}`. I *know* you want a self-contained example for the best chance of a good answer on SO, though. :) – twotwotwo Sep 05 '15 at 02:43
  • 1
    Nope, my initial comment was wrong; it's that you need to pass pointers into `Decode`/`Unmarshal` (complete examples still help though!). – twotwotwo Sep 05 '15 at 03:01

4 Answers4

38

Super late to the party here.

There is a popular testing package in golang called require github.com/stretchr/testify/require and it will do this for you.

func TestJsonEquality(t *testing.t) { 
  expected := `{"a": 1, "b": 2} `
  actual := ` {"b":   2, "a":   1}`
  require.JSONEq(t, expected, actual)
}

GoDocs: https://godoc.org/github.com/stretchr/testify/require#JSONEqf

Highstead
  • 2,291
  • 3
  • 26
  • 30
  • Also a bit late for the comment here ...Is there a way to exclude some parts from json comparison with JSONEq function ? I cannot find something relevant at the godoc. – Makis Papapanagiotou Nov 03 '21 at 17:54
  • 2
    This doesn't work when you have lists on JSON objects. If the items are out of order in the lists, the equality is false. – th3morg Jan 04 '22 at 20:40
  • 1
    @th3morg, a list is order dependent, so if the order in list mismach then the equality is false. – dangeroushobo Mar 28 '23 at 15:24
17

You need to pass pointers to Decode and Unmarshal. I put up a runnable sample with func JSONEqual(a, b io.Reader) and JSONBytesEqual(a, b []byte), both returning (bool, error). You can compare a request body to your static expected content (like you're trying to do in the question) by wrapping your expected content using bytes.NewBuffer or strings.NewReader. Here's the code:

package main

import (
    "encoding/json"
    "fmt"
    "io"
    "reflect"
)

// JSONEqual compares the JSON from two Readers.
func JSONEqual(a, b io.Reader) (bool, error) {
    var j, j2 interface{}
    d := json.NewDecoder(a)
    if err := d.Decode(&j); err != nil {
        return false, err
    }
    d = json.NewDecoder(b)
    if err := d.Decode(&j2); err != nil {
        return false, err
    }
    return reflect.DeepEqual(j2, j), nil
}

// JSONBytesEqual compares the JSON in two byte slices.
func JSONBytesEqual(a, b []byte) (bool, error) {
    var j, j2 interface{}
    if err := json.Unmarshal(a, &j); err != nil {
        return false, err
    }
    if err := json.Unmarshal(b, &j2); err != nil {
        return false, err
    }
    return reflect.DeepEqual(j2, j), nil
}

func main() {
    a := []byte(`{"x": ["y",42]}`)
    b := []byte(`{"x":                  ["y",  42]}`)
    c := []byte(`{"z": ["y", "42"]}`)
    empty := []byte{}
    bad := []byte(`{this? this is a test.}`)

    eq, err := JSONBytesEqual(a, b)
    fmt.Println("a=b\t", eq, "with error", err)
    eq, err = JSONBytesEqual(a, c)
    fmt.Println("a=c\t", eq, "with error", err)
    eq, err = JSONBytesEqual(a, empty)
    fmt.Println("a=empty\t", eq, "with error", err)
    eq, err = JSONBytesEqual(a, bad)
    fmt.Println("a=bad\t", eq, "with error", err)
}

It outputs:

a=b  true with error <nil>
a=c  false with error <nil>
a=empty  false with error EOF
a=bad    false with error invalid character 't' looking for beginning of object key string
twotwotwo
  • 28,310
  • 8
  • 69
  • 56
  • I read there is no point to have a pointer to an interface as the interface{} is already a kind of pointer. Why is this different? – Anthony Hunt Sep 05 '15 at 03:15
  • Good question, and if you read that on StackOverflow it might be my fault and maybe I should revise that answer, heh. Interfaces, like slice values, are really little structures containing pointers. But here, we actually want to let `Unmarshal` *change what's in that little structure* (from `nil` to a `map[struct]interface{}` it'll have to allocate), so we have to give it a pointer to it. – twotwotwo Sep 05 '15 at 03:18
  • There is probably more to it than this, but an interface value is pointer-like when you're trying to avoid copies: if it refers to a big chunk of data, copying the interface value won't copy the data pointed to. So, for example, [the `b=a` here](http://play.golang.org/p/Hs6-_3bAi5) doesn't allocate 1000 bytes. You still need to use pointers when the thing pointed to is being modified -- that's why you need one here. – twotwotwo Sep 05 '15 at 03:27
  • (I just added some detail to the top section at http://stackoverflow.com/questions/23542989/pointers-vs-values-in-parameters-and-return-values/23551970#23551970 to try and avoid confusing any future readers about that.) – twotwotwo Sep 05 '15 at 03:37
  • Great! Thanks a lot for the answer! – Anthony Hunt Sep 05 '15 at 03:53
4

I wrote a tool for comparing http json-based responses and I do so ignoring order. You can take a look at the package that implements the comparison and grab the Equal function: https://github.com/emacampolo/gomparator/blob/master/json_util.go#L10

eg:

b1 := []byte(`{"x": {"t": 1, "s": 2}, "z": 1}`)
b2 := []byte(`{"z": 1, "x": {"s": 2, "t": 1}}`)

j1, _ := Unmarshal(b1)
j2, _ := Unmarshal(b2)
assert.True(t, Equal(j1, j2))
ecampolo
  • 41
  • 2
0

Consider using the package at https://pkg.go.dev/github.com/wI2L/jsondiff that helps computing the diff between two JSON documents as a series of RFC6902 (JSON Patch) operations. An example form the docs:

import "github.com/wI2L/jsondiff"

patch, err := jsondiff.Compare(pod, newPod)
if err != nil {
    // handle error
}
b, err := json.MarshalIndent(patch, "", "    ")
if err != nil {
    // handle error
}
os.Stdout.Write(b)
Amitabh
  • 162
  • 9