与数组相比,JSON有何优势?

5

我将尝试比较两个JSON文件,它们都有包含重复值的数组。

第一个JSON对象包含一个如下的数组:

"categories": [
            "May",
            "Apr",
            "Mar"
          ]

我的第二个JSON对象有一个如下的数组:

"categories": [
            "May",
            "May",
            "Apr",
            "Apr",
            "Mar",
            "Mar"
          ]

我正在使用可以在此链接中找到的平面图来比较JSON:使用guava比较JSON 这是我的一部分代码:
 private String smartJSONsCompare(JSONObject leftJson, JSONObject rightJson) {

    String result = "</br>";
    Gson gson = new Gson();
    Type type = new TypeToken<Map<String, Object>>(){}.getType();
    Map<String, Object> leftMap = gson.fromJson(leftJson.toString(), type);
    Map<String, Object> rightMap = gson.fromJson(rightJson.toString(), type);
    Map<String, Object> leftFlatMap = FlatMapUtil.flatten(leftMap);
    Map<String, Object> rightFlatMap = FlatMapUtil.flatten(rightMap);
    MapDifference<String, Object> difference = Maps.difference(leftFlatMap, rightFlatMap);
    StringBuilder SB = new StringBuilder("</br>");
    SB.append("Entries only on LEFT: </br>");
    difference.entriesOnlyOnLeft().forEach((key, value) -> SB.append(key + ": " + value + "</br>"));
    SB.append("Entries only on RIGHT: </br>");
    difference.entriesOnlyOnRight().forEach((key, value) -> SB.append(key + ": " + value + "</br>"));
    SB.append("Entries full difference : </br>");
    difference.entriesDiffering().forEach((key, value) -> SB.append(key + ": " + value + "</br>"));
    result = SB.toString();
    return result;
}

我希望你能更加智能地呈现它们的区别,换句话说:展示所有在比较的 JSON 中不匹配的对象/数组。缺失了什么或者添加了什么。
对于“categories”数组,我的代码返回一个不匹配的信息,但是没有以优雅的方式说明差异。
我该怎么做?

为什么在Map中使用对象而不是列表? - Joakim Danielson
我运行了你的代码,看起来工作正常,但是你说的消息不匹配是什么意思呢?请按照以下步骤操作:将你在控制台中得到的消息完全复制一遍,并说明其中有什么问题。另外,请说明你期望的输出结果是什么。 - Maytham Fahmi
所以这就是关于展示第二个JSON中与第一个JSON相比的重复项。 - Maytham Fahmi
是的,但要以巧妙的方式展示差异。 - Tal Angel
1
可能 Spring sync 可以帮助你。 - senseiwu
显示剩余6条评论
5个回答

6

我稍微修改了你的解决方案,以获得所需的结果。

我会在列表中进行差异检查,因此我将根据您的代码创建一种将JSON更改为字符串列表的方法:

private static List<String> jsonToList(String json){
    List<String> list = new ArrayList<>();
    Gson gson = new Gson();
    Type type = new TypeToken<Map<String, Object>>(){}.getType();
    Map<String, Object> jsonMap = gson.fromJson(json, type);
    Map<String, Object> flatten = FlatMapUtil.flatten(jsonMap);
    flatten.forEach((k, v) -> list.add(v.toString()));
    return list;
}

更新
当我回答这个问题时,我有点匆忙,jsonToList是基于你的代码。目前它对你所要求的太过复杂了。因此,我使用以下方法制作了一个更轻量级的版本:

private static List<String> jsonToList(String json) {
    JSONObject response = new JSONObject(json);
    List<String> list = new ArrayList<>();
    JSONArray jsonArray = response.getJSONArray("categories");
    if (jsonArray != null) {
        for (int i = 0; i < jsonArray.length(); i++) {
            list.add(jsonArray.get(i).toString());
        }
    }
    return list;
}

话虽如此,现在你有两个选择,取决于你找出哪一个最适合你的需求,并从这里开始进行。

更新结束


针对这个示例,我制作了三个测试例子。

String main = "{\"categories\":[\"May\",\"Apr\",\"Mar\"]}";
String json1 = "{\"categories\":[\"May\",\"May\",\"Apr\",\"Apr\",\"Mar\",\"Mar\"]}";
String json2 = "{\"categories\":[\"May\",\"Apr\",\"Apr\",\"Mar\",\"Mar\",\"Mar\"]}";
String json3 = "{\"categories\":[\"May\",\"Apr\",\"Mar\",\"Mar\"]}";

在我的第二步中,我将创建一个:

List<String> mainList = jsonToList(main);
List<String> list1 = jsonToList(json1);

目前为止一切顺利。现在我编写了一个方法来获取这两个列表中的额外差异,也就是您在评论中请求的,我们只取所有重复超过一次的值,并将它们以列表形式返回。在这个方法中,我仅使用哈希映射来计算重复项,然后获取所有重复出现超过1次的项:

private static List<String> diffList(List<String> mainList, List<String> secondList){
    List<String> list = new ArrayList<String>();
    Map<String, Integer> wordCount = new HashMap<>();
    for(String word: secondList) {
        if(mainList.contains(word)) {
            Integer count = wordCount.get(word);
            wordCount.put(word, (count == null) ? 1 : count + 1);
            if(wordCount.get(word) > 1){
                list.add(word);
            }
        }
    }
    return list;
}

最后我会测试所有的情况,比如对于list1:

List<String> diff1 = diffList(mainList, list1);
for (String s : diff1) {
    System.out.println(s);
}

输出结果将会是:
May
Apr
Mar

对于list2

Apr
Mar
Mar

对于列表3

Mar

现在我将从你的方法中分离出视图方法,并创建类似以下内容的东西,只是为了使我的代码更清晰易于操作:
private static String viewResult(List<String> list1, List<String> list2, List<String> duplicate){
    String result;
    StringBuilder SB = new StringBuilder("</br>");
    SB.append("Entries only on LEFT: </br>");
    list1.forEach(e -> SB.append(e + "</br>"));
    SB.append("Entries only on RIGHT: </br>");
    list2.forEach(e -> SB.append(e + "</br>"));
    SB.append("Entries full difference : </br>");
    duplicate.forEach(e -> SB.append(e + "</br>"));
    result = SB.toString();
    return result;
}

所以如果我们把所有代码放在一起,它会变成这样,下面的代码是为了演示事物如何运作,但你可以在你的代码中将其提升到下一级:

public static void main(String[] args) {
    String main = "{\"categories\":[\"May\",\"Apr\",\"Mar\"]}";
    String json1 = "{\"categories\":[\"May\",\"May\",\"Apr\",\"Apr\",\"Mar\",\"Mar\"]}";
    String json2 = "{\"categories\":[\"May\",\"Apr\",\"Apr\",\"Mar\",\"Mar\",\"Mar\"]}";
    String json3 = "{\"categories\":[\"May\",\"Apr\",\"Mar\",\"Mar\"]}";

    List<String> mainList = jsonToList(main);

    List<String> list1 = jsonToList(json1);
    List<String> diff1 = diffList(mainList, list1);
    for (String s : diff1) {
        System.out.println(s);
    }

    String view = viewResult(mainList, list1, diff1);
}

private static List<String> jsonToList(String json){
    List<String> list = new ArrayList<String>();
    Gson gson = new Gson();
    Type type = new TypeToken<Map<String, Object>>(){}.getType();
    Map<String, Object> jsonMap = gson.fromJson(json, type);
    Map<String, Object> flatten = FlatMapUtil.flatten(jsonMap);
    flatten.forEach((k, v) -> list.add(v.toString()));
    return list;
}

private static List<String> diffList(List<String> mainList, List<String> secondList){
    List<String> list = new ArrayList<String>();
    Map<String, Integer> wordCount = new HashMap<>();
    for(String word: secondList) {
        if(mainList.contains(word)) {
            Integer count = wordCount.get(word);
            wordCount.put(word, (count == null) ? 1 : count + 1);
            if(wordCount.get(word) > 1){
                list.add(word);
            }
        }
    }
    return list;
}

private static String viewResult(List<String> list1, List<String> list2, List<String> duplicate){
    String result;
    StringBuilder SB = new StringBuilder("</br>");
    SB.append("Entries only on LEFT: </br>");
    list1.forEach(e -> SB.append(e + "</br>"));
    SB.append("Entries only on RIGHT: </br>");
    list2.forEach(e -> SB.append(e + "</br>"));
    SB.append("Entries full difference : </br>");
    duplicate.forEach(e -> SB.append(e + "</br>"));
    result = SB.toString();
    return result;
}

2
如果你想要一个更通用且具有良好差异性的工具,可以在这里使用AssertJ。它通常用于测试,但是差异看起来非常好,你也可以在正常代码中使用它。
例如:
Expecting:
  <["Mai", "Apr", "Mar"]>
to contain exactly in any order:
  <["May", "Apr", "Mar", "Mar"]>
elements not found:
  <["May", "Mar"]>
and elements not expected:
  <["Mai"]>

可以通过以下方式创建:

[...]
import org.assertj.core.api.Assertions;

public class JsonTest {
    final static String arr = " [\n"+
            "            \"Mai\",\n"+
            "            \"Apr\",\n"+
            "            \"Mar\"\n"+
            "          ]";
    final static String arr2 = " [\n"+
            "            \"May\",\n"+
            "            \"Apr\",\n"+
            "            \"Mar\",\n"+
            "            \"Mar\"\n"+
            "          ]";

    public static void main(String[] args){
        System.out.println(smartJSONsCompare(arr,arr2));
    }

    private static String smartJSONsCompare(String leftJson, String rightJson) {
        Gson gson = new Gson();
        Type type = new TypeToken<List<String>>(){}.getType();
        List<String> left = gson.fromJson(leftJson, type);
        List<String> right = gson.fromJson(rightJson, type);
        try{
            Assertions.assertThat(left).containsExactlyInAnyOrderElementsOf(right);
        }catch(AssertionError  ae){
            return ae.getMessage();
        }
        return "Matched";
    }
  }

我使用gradle添加了以下依赖:

dependencies {
    compile("org.assertj:assertj-core:3.11.1")
}

2
如果您想在两个JSON对象之间创建一个补丁,请查看json-patch。"最初的回答"。
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.github.fge.jsonpatch.diff.JsonDiff;
import java.io.IOException;

public class JsonPatchTest {
    public static void main(String[] args) throws IOException {
        String jsonFirst = "{\"categories\":[\"May\",\"Apr\",\"Mar\"]}";
        String jsonSecond = "{\"categories\":[\"May\",\"May\",\"Apr\",\"Apr\",\"Mar\",\"Mar\"]}";

        ObjectMapper mapper = new ObjectMapper();
        JsonNode jsonNodeFirst = mapper.readTree(jsonFirst);
        JsonNode jsonNodeSecond = mapper.readTree(jsonSecond);

        JsonNode patchNode = JsonDiff.asJson(jsonNodeFirst, jsonNodeSecond);

        System.out.println(mapper.writerWithDefaultPrettyPrinter().writeValueAsString(patchNode));
    }
}

您的情况下,会产生以下输出:

最初的回答

[ {
  "op" : "replace",
  "path" : "/categories/1",
  "value" : "May"
}, {
  "op" : "replace",
  "path" : "/categories/2",
  "value" : "Apr"
}, {
  "op" : "add",
  "path" : "/categories/-",
  "value" : "Apr"
}, {
  "op" : "add",
  "path" : "/categories/-",
  "value" : "Mar"
}, {
  "op" : "add",
  "path" : "/categories/-",
  "value" : "Mar"
} ]

0

我认为你应该自己处理JSON数组,以便以更“智能”的方式呈现它们的差异。这里有一个包含CollectionUtils类和disjunction方法的库。

MapDifference<String, Object> difference = Maps.difference(leftMap, rightMap);
difference.entriesDiffering().forEach((key, value) -> {
  Object left = value.leftValue();
  Object right = value.rightValue();
  if (left instanceof Iterable && right instanceof Iterable) {
    Collection<?> diff = CollectionUtils.disjunction((Iterable<?>) right, (Iterable<?>) left);
    System.out.println(key + " -> " + diff);
  }
  ...
});

0

这段代码在我两年前的生产环境中可行。

public class App {
private final Gson GSON = new GsonBuilder().create();

public boolean isDifference(final String path, Map<String, Object> oldData, Map<String, Object> newData) {
    MapDifference<String, Object> difference = Maps.difference(oldData, newData);
    difference.entriesOnlyOnLeft().forEach((key, value) -> {
        publishChange(Action.REMOVE, path, key, value);
    });
    difference.entriesOnlyOnRight().forEach((key, value) -> {
        publishChange(Action.ADD, path, key, value);
    });
    difference.entriesDiffering().forEach((key, value) -> {
        if (value.rightValue() instanceof Map && value.leftValue() instanceof Map) {
            if (!path.isEmpty()) {
                key = path.concat("-").concat(key);
            }
            isDifference(key, (Map) value.leftValue(), (Map) value.rightValue());
        } else {
            publishChange(Action.MODIFY, path, key, value);
        }
    });
    return !difference.areEqual();
}

public void publishChange(Action action, String path, String key, Object value) {
    if (value instanceof MapDifference.ValueDifference) {
        value = ((MapDifference.ValueDifference) value).rightValue();
    }
    JsonElement jsonValue = GSON.toJsonTree(value);
    String event = createEvent(action, path, key, jsonValue);
    System.out.println("Differrence: " + event);
}

public String createEvent(Action action, String paths, String key, JsonElement value) {
    JsonObject root = new JsonObject();
    JsonArray arrPaths = new JsonArray();
    for (String path : paths.split("-")) {
        arrPaths.add(path);
    }
    root.addProperty("action", action.toString());
    root.add("paths", arrPaths);
    JsonObject data = new JsonObject();
    data.addProperty("key", key);
    data.add("value", value);
    root.add("data", data);
    return root.toString();
}

public static enum Action {
    ADD, REMOVE, MODIFY
}}

测试/示例:

public class AppTest {
@Test
public void testAppHasAGreeting() {
    App classUnderTest = new App();

    Gson gson = new GsonBuilder()
            .setPrettyPrinting()
            .create();
    // JsonOld: {"a":1,"b":1,"c":true,"array":[1,2,3],"object":{"arrayKey":["a","b","c","d"]}}
    String jsonOld = "{\"a\":1,\"b\":1,\"c\":true,\"array\":[1,2,3],\"object\":{\"arrayKey\":[\"a\",\"b\",\"c\",\"d\"]}}";
    // JsonNew: {"a":2,"b":1,"array":[1,2,3,2],"another":{"d":false,"e":["a","b","c"]},"object":{"booleanKey":true,"arrayKey":["a","b","c"]}}
    String jsonNew = "{\"a\":2,\"b\":1,\"array\":[1,2,3,2],\"another\":{\"d\":false,\"e\":[\"a\",\"b\",\"c\"]},\"object\":{\"booleanKey\":true,\"arrayKey\":[\"a\",\"b\",\"c\"]}}";

    Type mapType = new TypeToken<Map<String, Object>>() {
    }.getType();
    Map<String, Object> jsonOldAsMap = gson.fromJson(jsonOld, mapType);
    Map<String, Object> jsonNewAsMap = gson.fromJson(jsonNew, mapType);
    System.out.println("Old Json: " + gson.toJson(jsonOldAsMap));
    System.out.println("New Json: " + gson.toJson(jsonNewAsMap));
    System.out.println("========== Result ==========");
    // When
    boolean diff = classUnderTest.isDifference("", jsonOldAsMap, jsonNewAsMap);
    // Then
    assertTrue(diff);
}}

结果将会打印如下:

差异:{"action":"REMOVE","paths":[""],"data":{"key":"c","value":true}}

差异:{"action":"ADD","paths":[""],"data":{"key":"another","value":{"d":false,"e":["a","b","c"]}}}

差异:{"action":"MODIFY","paths":[""],"data":{"key":"a","value":2.0}}

差异:{"action":"MODIFY","paths":[""],"data":{"key":"array","value":[1.0,2.0,3.0,2.0]}}

差异:{"action":"ADD","paths":["object"],"data":{"key":"booleanKey","value":true}}

差异:{"action":"MODIFY","paths":["object"],"data":{"key":"arrayKey","value":["a","b","c"]}}

代码可在此处找到:https://github.com/liemle3893/compare-json


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接