【问题标题】:How to compare two JSON requests?如何比较两个 JSON 请求?
【发布时间】:2015-12-01 06:02:27
【问题描述】:

短篇小说:如何比较两个 JSON 块?下面的代码出错了。

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
}

长篇大论: 我正在做一些 E2E 测试,其中一部分我需要将请求的 JSON 正文与收到的 JSON 进行比较。为此,我尝试将预期和接收到的 json 解组到一个空接口(以避免任何类型错误),但出现错误: json: Unmarshal(nil)。我猜 encoding/json 不喜欢空接口所以问题是如何比较两个 JSON 块?字符串比较容易出错,所以我试图避免这种情况。

【问题讨论】:

  • 认为您只需将声明更改为j, j2 := map[string]interface{}{}, map[string]interface{}{}。不过,我知道您想要一个独立的示例,以便在 SO 上有一个好的答案。 :)
  • 不,我最初的评论是错误的;就是您需要将指针传递给Decode/Unmarshal(尽管完整的示例仍然有帮助!)。

标签: json go


【解决方案1】:

在这里参加派对超级迟到。

在 golang 中有一个流行的测试包叫做 require github.com/stretchr/testify/require,它会为你做这个。

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

【讨论】:

  • 这里的评论也有点晚了......有没有办法从与 JSONEq 函数的 json 比较中排除某些部分?我在 godoc 上找不到相关内容。
  • 当您有 JSON 对象的列表时,这不起作用。如果列表中的项目无序,则相等为假。
【解决方案2】:

您需要将指针传递给DecodeUnmarshal。我用func JSONEqual(a, b io.Reader)JSONBytesEqual(a, b []byte) 设置了runnable sample,两者都返回(bool, error)。您可以通过使用bytes.NewBufferstrings.NewReader 包装您的预期内容,将请求正文与您的静态预期内容(就像您在问题中尝试做的那样)进行比较。代码如下:

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)
}

它输出:

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

【讨论】:

  • 我读到没有指向接口的指针,因为 interface{} 已经是一种指针。为什么会有所不同?
  • 好问题,如果你在 StackOverflow 上读到它,那可能是我的错,也许我应该修改那个答案,呵呵。接口,就像切片值一样,实际上是包含指针的小结构。但是在这里,我们实际上想让Unmarshal 改变那个小结构中的内容(从nilmap[struct]interface{} 它必须分配),所以我们必须给它一个指针给它。
  • 可能不止于此,但是当您试图避免复制时,接口值类似于指针:如果它引用大量数据,则复制接口值不会' t 复制指向的数据。因此,例如,the b=a here 不会分配 1000 个字节。当指向的东西被修改时,你仍然需要使用指针——这就是为什么你需要一个。
  • (我刚刚在stackoverflow.com/questions/23542989/… 的顶部添加了一些细节,以避免让未来的读者对此感到困惑。)
  • 太棒了!非常感谢您的回答!
【解决方案3】:

我编写了一个用于比较基于 http json 的响应的工具,但我忽略了顺序。您可以查看实现比较的包并获取 Equal 函数: https://github.com/emacampolo/gomparator/blob/master/json_util.go#L10

例如:

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))

【讨论】:

    猜你喜欢
    • 2022-07-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-11-29
    • 2017-04-06
    • 2020-12-28
    • 2012-12-26
    • 2020-07-22
    相关资源
    最近更新 更多