Blease
Blease

Reputation: 1420

Jackson - Deserialise nested JSON String

In my elasticsearch cluster I have a document that looks like this:

{
   "@version":"1",
   "@timestamp":"2021-04-12T14:50:40.298Z",
   "message":"{\"@class\": \"com.foobar.PriceChangeEvent\", \"price\":\"4.65\", \"currency\":\"GBP\", \"product\": \"1209381842\", \"meta\": {\"user\": \"TomScott\", \"service\": \"price-manager\"}}",
   "exchange":"PriceIncrease",
   "service":"price-manager",
   "env":"test",
   "type":"event",
   "user":"TomScott"
}

And a class with the following:

@JsonIgnoreProperties(ignoreUnknown = true)
public class ElasticsearchEventEntry<T extends BaseEvent> {

    @JsonProperty("service")
    private String service;

    @JsonProperty("@timestamp")
    @JsonDeserialize(using = LocalDateTimeDeserializer.class)
    @JsonSerialize(using = LocalDateTimeSerializer.class)
    private LocalDateTime timestamp;

    @JsonProperty("env")
    private String environment;

    @JsonProperty("exchange")
    private String exchange;

    @JsonProperty("message")
    private T event;

    @JsonProperty("user")
    private String user;
}

Being read using the current method:

ElasticsearchEventEntry<PriceChangeEvent> entry = mapper.readValue(elasticResponse, new TypeReference<ElasticsearchEventEntry<PriceChangeEvent>>() {});

However the message is not parsed straight into a PriceChangeEvent which is understandable as it's a string not "proper" JSON. I have tried to implement a custom deserialiser which would be able to convert the JSON to the event without any luck. I've attempted to create a custom deserialiser however it's not working with the polymorphic type.

Upvotes: 1

Views: 552

Answers (1)

Blease
Blease

Reputation: 1420

I finally got the custom deserialiser to work for me:

public class ElasticsearchEventMessageDeserialiser extends JsonDeserializer<BaseEvent> {
    @Override
    public BaseEvent deserialize(JsonParser jsonParser, DeserializationContext context) throws IOException {
        TextNode textNode = jsonParser.readValueAsTree();
        ObjectMapper mapper = (ObjectMapper) jsonParser.getCodec();
        return mapper.readValue(textNode.textValue(), BaseEvent.class);
    }
}

And simply annotated the field with the new deserialiser:

@JsonIgnoreProperties(ignoreUnknown = true)
public class ElasticsearchEventEntry<T extends BaseEvent> {

    @JsonProperty("service")
    private String service;

    @JsonProperty("@timestamp")
    @JsonDeserialize(using = LocalDateTimeDeserializer.class)
    @JsonSerialize(using = LocalDateTimeSerializer.class)
    private LocalDateTime timestamp;

    @JsonProperty("env")
    private String environment;

    @JsonProperty("exchange")
    private String exchange;

    @JsonProperty("message")
    @JsonDeserialize(using = ElasticsearchEventMessageDeserialiser.class)
    @JsonTypeInfo(use = Id.NONE)
    private T event;

    @JsonProperty("user")
    private String user;
}

Upvotes: 1

Related Questions