Shabar
Shabar

Reputation: 2823

Compare two json responses without values with Jackson

Is that possible to compare two json responses with Jackson lib ignoring all values.

So below will assert both attribute and values.

{
    "employee":
    {
        "id": "1212",
        "fullName": "John Miles",
        "age": 34
    }
}

Code:

ObjectMapper mapper = new ObjectMapper();
assertEquals(mapper.readTree(s1), mapper.readTree(s2));

Upvotes: 0

Views: 227

Answers (1)

Turing85
Turing85

Reputation: 20185

A quick lookup did not yield any results that Jackson has built-in capabilites for this functionality. We can, however, write our own checker for this task to compare two Map<String, ?>s in such a way that we return true iff.:

  • the keySet() of both Maps are equal
  • the keySet() of those objects that are instanceof Map of both Maps are equal
  • the values which are Maps must be structurally equally aswell (recursive call)

For the analysis, we have to make the assumption that if a Map is a value in another Map, it must be a Map<String, ?> since our solution will make an unchecked cast with this assumption.

With this assumption, a solution may look like this:

@SuppressWarnings("unhecked")
public static boolean areStructuralEqual(Map<String, ?> left, Map<String, ?> right) {
    if (!Objects.equals(left.keySet(), right.keySet())) {
        return false;
    }

    Set<String> leftKeysWithMapValues = extractAllKeysThatMapToMaps(left);
    Set<String> rightKeysWithMapValues = extractAllKeysThatMapToMaps(right);
    if (!Objects.equals(leftKeysWithMapValues, rightKeysWithMapValues)) {
        return false;
    }

    for (String key : leftKeysWithMapValues) {
        if (!areStructuralEqual(
                (Map<String, ?>) left.get(key),
                (Map<String, ?>) right.get(key))) {
            return false;
        }
    }
    return true;
}

private static Set<String> extractAllKeysThatMapToMaps(Map<String, ?> map) {
    return map.entrySet().stream()
            .filter(e -> e.getValue() instanceof Map)
            .map(Entry::getKey)
            .collect(Collectors.toUnmodifiableSet());
}

Ideone demo

Notice that this solution does not verify that the types of the keys are the same. Also, if the value of a key is set to null in one Map and the key is not present in the other Map, the comparison will return false. The latter can be fixed by filtering out keys whose values are null:

@SuppressWarnings("unhecked")
public static boolean areStructuralEqual(Map<String, ?> left, Map<String, ?> right) {
    Set<Entry<String, ?>> leftEntriesWithNonNullValues = 
            extractEntriesWithNonNullValues(left);
    Set<Entry<String, ?>> rightEntriesWithNonNullValues =
            extractEntriesWithNonNullValues(right);
    Set<String> leftKeysWithNonNullValues = leftEntriesWithNonNullValues.stream()
            .map(Entry::getKey)
            .collect(Collectors.toUnmodifiableSet());
    Set<String> rightKeysWithNonNullValues = rightEntriesWithNonNullValues.stream()
            .map(Entry::getKey)
            .collect(Collectors.toUnmodifiableSet());
    if (!Objects.equals(leftKeysWithNonNullValues, rightKeysWithNonNullValues)) {
        return false;
    }

    Set<String> leftKeysWithMapValues =
            extractAllKeysThatMapToMaps(leftEntriesWithNonNullValues);
    Set<String> rightKeysWithMapValues =
            extractAllKeysThatMapToMaps(rightEntriesWithNonNullValues);
    if (!Objects.equals(leftKeysWithMapValues, rightKeysWithMapValues)) {
        return false;
    }

    for (String key : leftKeysWithMapValues) {
        if (!areStructuralEqual(
                (Map<String, ?>) left.get(key),
                (Map<String, ?>) right.get(key))) {
            return false;
        }
    }
    return true;
}

private static Set<String> extractAllKeysThatMapToMaps(Set<Entry<String, ?>> entrySet) {
    return entrySet.stream()
            .filter(e -> e.getValue() instanceof Map)
            .map(Entry::getKey)
            .collect(Collectors.toUnmodifiableSet());
}

private static Set<Entry<String, ?>> extractEntriesWithNonNullValues(Map<String, ?> map) {
    return map.entrySet().stream()
            .filter(e -> Objects.nonNull(e.getValue()))
            .collect(Collectors.toUnmodifiableSet());
}

Ideone demo

Upvotes: 1

Related Questions