Home > Software engineering >  JSON Patch diffing fails due to ObjectMapper?
JSON Patch diffing fails due to ObjectMapper?

Time:05-19

I have 2 objects that I'm trying to diff. For this purpose I use Jackson ObjectMapper in spring-boot version 2.1.3 to de-serialize them to a String, read them as a tree (convert to JsonNode) and then diff them to create a JsonPatch object. What I do notice is that in the existing object's JsonNode all the fields having null values are skipped as are objects having only null valued fields. The ObjectMapper seems to be behaving differently in the consumer (spring-boot version 2.6.7) and service provider (spring-boot-2.1.3). Sample, within the tree:

"_optionalAttrs":{"styles":{"99_0002_4_24_002":{"hineck":null,"choices":{"99_0002_4_24_002_001":{"color":null}}},"99_0002_4_24_001":{"hineck":null,"choices":null}}.   

vs

"_optionalAttrs":{"styles":{"99_0002_4_24_002":{"choices":{"99_0002_4_24_002_001":{}}},"99_0002_4_24_001":{}}

I am guessing this is the reason why the JsonPatch operations generated are incorrect :

op: copy; from: "/_optionalAttrs/styles/99_0002_4_24_001/hineck"; path: "/_optionalAttrs/clientAttributes/channel"

and I end up getting this error - no such path in target JSON document. Is there a way to ensure that the two remain consistent? If you think there are any other issues please let me know. The consumer code is something we can change but the service provider code is not in our ownership. I am using json-patch-1.12 and jdk 11.

CodePudding user response:

By default, ObjectMapper will keep null values, but you can skip them using the following setting:

objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL)

CodePudding user response:

I have not good solution, but it's better then nothing You can map object to Map<String, String> with fieldAndValueMap method and compare them

    public Map<String, String> fieldAndValueMap(Class<?> type, Object obj) throws ReflectiveOperationException {
        Map<String, String> result = new HashMap<>();
        for (Field field : type.getDeclaredFields()) {
            field.setAccessible(true);
            if (isWrapperType(field.getType())) {
                String value = nonNull(field.get(obj)) ? field.get(obj).toString() : "null";
                result.put(type.getSimpleName()   "."   field.getName(), value);
            } else {
                String getMethod = "get"   Character.toUpperCase(field.getName().charAt(0))   field.getName().substring(1);
                Method method = type.getMethod(getMethod);
                Object subObj = method.invoke(obj);
                if (nonNull(subObj)) {
                    result.putAll(fieldAndValueMap(field.getType(), subObj));
                }
            }
        }
        return result;
    }

    public boolean isWrapperType(Class<?> type) {
        return getWrapperTypes().contains(type);
    }

    private Set<Class<?>> getWrapperTypes() {
        Set<Class<?>> ret = new HashSet<>();
        ret.add(Boolean.class);
        ret.add(Character.class);
        ret.add(String.class);
        ret.add(Byte.class);
        ret.add(Short.class);
        ret.add(Integer.class);
        ret.add(Long.class);
        ret.add(Float.class);
        ret.add(Double.class);
        ret.add(Void.class);
        ret.add(BigDecimal.class);
        ret.add(LocalDate.class);
        ret.add(LocalDateTime.class);
        ret.add(LocalTime.class);
        ret.add(Date.class);
        return ret;
    }
to get object as map of fields and values: fieldAndValueMap(firstObject.getClass(), firstObject)

  • Related