Jackson. Как прервать десериализацию объекта и передать в поле вложенный объект в качестве строки JSON?

Необходимо с помщью Jackson десериализовать объект, но так, чтоб в некотором поле десериализация прекратилась, и вложенный json записался в поле в виде строки.

@Data
public class A {
    private String a;

    public static void main(String[] args) throws JsonProcessingException {
        final ObjectMapper mapper = new ObjectMapper();
        final String json = "{\"a\": {\"b\": 123}}";

        final A a = mapper.readValue(json, A.class);

        System.out.println(a);
    }
}

В примере ожидаю, что в поле a будет содержать строку со значением "{\"b\": 123}". Но если просто указать тип поля String, то возникает следующая ошибка:

Exception in thread "main" com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize value of type `java.lang.String` from Object value (token `JsonToken.START_OBJECT`)
at [Source: (String)"{"a": {"b": 123}}"; line: 1, column: 7] (through reference chain: A["a"])
                at com.fasterxml.jackson.databind.exc.MismatchedInputException.from(MismatchedInputException.java:59)
                at com.fasterxml.jackson.databind.DeserializationContext.reportInputMismatch(DeserializationContext.java:1741)
                at com.fasterxml.jackson.databind.DeserializationContext.handleUnexpectedToken(DeserializationContext.java:1515)
                at com.fasterxml.jackson.databind.DeserializationContext.handleUnexpectedToken(DeserializationContext.java:1420)
                at com.fasterxml.jackson.databind.DeserializationContext.extractScalarFromObject(DeserializationContext.java:932)
                at com.fasterxml.jackson.databind.deser.std.StringDeserializer.deserialize(StringDeserializer.java:62)
                at com.fasterxml.jackson.databind.deser.std.StringDeserializer.deserialize(StringDeserializer.java:11)
                at com.fasterxml.jackson.databind.deser.impl.MethodProperty.deserializeAndSet(MethodProperty.java:129)
                at com.fasterxml.jackson.databind.deser.BeanDeserializer.vanillaDeserialize(BeanDeserializer.java:313)
                at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:176)
                at com.fasterxml.jackson.databind.deser.DefaultDeserializationContext.readRootValue(DefaultDeserializationContext.java:322)
                at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4674)
                at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3629)
                at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3597)
                at A.main(A.java:24)

Эту проблему решить можно, если вручную написать десериализатор:

@Data
@JsonDeserialize(using = A.ADeserializer.class)
public class A {
    private String a;

    public static void main(String[] args) throws JsonProcessingException {
        final ObjectMapper mapper = new ObjectMapper();
        final String json = "{\"a\": {\"b\": 123}}";

        final A a = mapper.readValue(json, A.class);

        System.out.println(a); // A(a={"b":123})
    }
    
    static class ADeserializer extends StdDeserializer<A> {
        public ADeserializer() {
            this(null);
        }

        protected ADeserializer(Class<?> vc) {
            super(vc);
        }

        @Override
        public A deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JacksonException {
            final JsonNode node = p.getCodec().readTree(p);
            final String subJson = node.get("a").toString();
            final A a = new A();
            a.setA(subJson);
            return a;
        }
    }
}

Но это решение очень громоздкое даже для одного поля.

Есть ли в Jackson какое-то более компактное решение этой проблемы из коробки?


Ответы (1 шт):

Автор решения: Roman-Stop RU aggression in UA

Создайте десериализатор для типа String:

class StringDeserializer extends StdDeserializer<String> {
    public StringDeserializer() {
        this(null);
    }

    protected ADeserializer(Class<?> vc) {
        super(vc);
    }

    @Override
    public String deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JacksonException {
        final JsonNode node = p.getCodec().readTree(p);
        return node.toString();
    }
}

И используйте его на любых полях String любых классов:

@Data
public class A {
    @JsonDeserialize(using = StringDeserializer.class)
    private String a;
    ...
}
→ Ссылка