0

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.

Blease
  • 1,380
  • 4
  • 38
  • 64
  • You need to run deserialisation process for message manually using custom deserialiser. Take a look at [How can I deserialize a nested wrapped String in Jackson?](https://stackoverflow.com/a/66188646/51591). If you want to use `@class` property during deserialisation take a look at `@JsonTypeInfo` and `@JsonSubTypes` annotation, [Inheritance with Jackson](https://www.baeldung.com/jackson-inheritance) – Michał Ziober May 11 '21 at 05:32
  • 1
    Cheers @MichałZiober, I managed to figure out something similar before seeing this. I have answered my own question for any future readers. – Blease May 11 '21 at 13:28

1 Answers1

1

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;
}
Blease
  • 1,380
  • 4
  • 38
  • 64