How to compare JSON documents and return the differences with Jackson or Gson?

后端 未结 2 1418
悲哀的现实
悲哀的现实 2020-12-24 08:35

I am using spring-boot to develop backend services. There is a scenario to compare 2-beans(one is the DB object and another one is the client requested object) and return th

2条回答
  •  有刺的猬
    2020-12-24 09:13

    Reading the JSON documents as Maps and comparing them

    You could read both JSON documents as Map. See the below examples for Jackson and Gson:

    ObjectMapper mapper = new ObjectMapper();
    TypeReference> type = 
        new TypeReference>() {};
    
    Map leftMap = mapper.readValue(leftJson, type);
    Map rightMap = mapper.readValue(rightJson, type);
    
    Gson gson = new Gson();
    Type type = new TypeToken>(){}.getType();
    
    Map leftMap = gson.fromJson(leftJson, type);
    Map rightMap = gson.fromJson(rightJson, type);
    

    Then use Guava's Maps.difference(Map, Map) to compare them. It returns a MapDifference instance:

    MapDifference difference = Maps.difference(leftMap, rightMap);
    

    If you are not happy with the result, you can consider flattening the maps and then compare them. It will provide better comparison results especially for nested objects and arrays.

    Creating flat Maps for the comparison

    To flat the map, you can use:

    public final class FlatMapUtil {
    
        private FlatMapUtil() {
            throw new AssertionError("No instances for you!");
        }
    
        public static Map flatten(Map map) {
            return map.entrySet().stream()
                    .flatMap(FlatMapUtil::flatten)
                    .collect(LinkedHashMap::new, (m, e) -> m.put("/" + e.getKey(), e.getValue()), LinkedHashMap::putAll);
        }
    
        private static Stream> flatten(Map.Entry entry) {
    
            if (entry == null) {
                return Stream.empty();
            }
    
            if (entry.getValue() instanceof Map) {
                return ((Map) entry.getValue()).entrySet().stream()
                        .flatMap(e -> flatten(new AbstractMap.SimpleEntry<>(entry.getKey() + "/" + e.getKey(), e.getValue())));
            }
    
            if (entry.getValue() instanceof List) {
                List list = (List) entry.getValue();
                return IntStream.range(0, list.size())
                        .mapToObj(i -> new AbstractMap.SimpleEntry(entry.getKey() + "/" + i, list.get(i)))
                        .flatMap(FlatMapUtil::flatten);
            }
    
            return Stream.of(entry);
        }
    }
    

    It uses the JSON Pointer notation defined in the RFC 6901 for the keys, so you can easily locate the values.

    Example

    Consider the following JSON documents:

    {
      "name": {
        "first": "John",
        "last": "Doe"
      },
      "address": null,
      "birthday": "1980-01-01",
      "company": "Acme",
      "occupation": "Software engineer",
      "phones": [
        {
          "number": "000000000",
          "type": "home"
        },
        {
          "number": "999999999",
          "type": "mobile"
        }
      ]
    }
    
    {
      "name": {
        "first": "Jane",
        "last": "Doe",
        "nickname": "Jenny"
      },
      "birthday": "1990-01-01",
      "occupation": null,
      "phones": [
        {
          "number": "111111111",
          "type": "mobile"
        }
      ],
      "favorite": true,
      "groups": [
        "close-friends",
        "gym"
      ]
    }
    

    And the following code to compare them and show the differences:

    Map leftFlatMap = FlatMapUtil.flatten(leftMap);
    Map rightFlatMap = FlatMapUtil.flatten(rightMap);
    
    MapDifference difference = Maps.difference(leftFlatMap, rightFlatMap);
    
    System.out.println("Entries only on the left\n--------------------------");
    difference.entriesOnlyOnLeft()
              .forEach((key, value) -> System.out.println(key + ": " + value));
    
    System.out.println("\n\nEntries only on the right\n--------------------------");
    difference.entriesOnlyOnRight()
              .forEach((key, value) -> System.out.println(key + ": " + value));
    
    System.out.println("\n\nEntries differing\n--------------------------");
    difference.entriesDiffering()
              .forEach((key, value) -> System.out.println(key + ": " + value));
    

    It will produce the following output:

    Entries only on the left
    --------------------------
    /address: null
    /phones/1/number: 999999999
    /phones/1/type: mobile
    /company: Acme
    
    
    Entries only on the right
    --------------------------
    /name/nickname: Jenny
    /groups/0: close-friends
    /groups/1: gym
    /favorite: true
    
    
    Entries differing
    --------------------------
    /birthday: (1980-01-01, 1990-01-01)
    /occupation: (Software engineer, null)
    /name/first: (John, Jane)
    /phones/0/number: (000000000, 111111111)
    /phones/0/type: (home, mobile)
    

提交回复
热议问题