0

I am reading from a stream that provides updates to an order book used to calculate market depth. Each includes a list of new entries for the order book. Each entry contains three properties.

  1. Market side (e.g. buy or sell)
  2. Quantity transacted of the commodity
  3. Unit price of the commodity in this transaction

These entries are represented in JSON as array nodes. Here is an example of how entries might be provided.

{
    "Changes": [
        { "entry": ["buy","470.84724800000004","16.14963"] },
        { "entry": ["buy","470.787392","0.01"] },
        { "entry": ["sell","473.112752","9.325423"] },
        { "entry": ["sell","473.052608","11.80723"] }
    ],
    ...some more fields; not relevant to this question...
}

As you can see, the indices of each entry array are used as field names. The position of each array element defines what property it represents. The side is at index 0, the unit price is at index 1, and the quantity is at index 2.

How can I de/serialize these using arrays Jackson annotations in Java 8? I am only asking about the innermost arrays. I don't need help with the object structure in general.

I tried making a class similar to the following.

public class OrderBookEntry {
    final String side;
    final BigDecimal price;
    final BigDecimal quantity;

    @JsonCreator
    public OrderBookEntry(@JsonProperty(index = 0, required = true) String side,
                          @JsonProperty(index = 1, required = true) BigDecimal price,
                          @JsonProperty(index = 2, required = true) BigDecimal quantity) {
        this.side = side;
        this.price = price;
        this.quantity = quantity;
    }
}

I have tried specifying @JsonFormat.Shape.ARRAY on the class. Every time I try to deserialize a sample string, I get an InvalidDefinitionException.

Exception in thread "main" com.fasterxml.jackson.databind.exc.InvalidDefinitionException:

Invalid type definition for type com.example.OrderBookEntry: Argument #0 has no property name, is not Injectable: can not use as Creator [constructor for com.example.OrderBookEntry, annotations: {interface com.fasterxml.jackson.annotation.JsonCreator=@com.fasterxml.jackson.annotation.JsonCreator(mode=DEFAULT)}] at [Source: (String)"["buy","470.84724800000004","16.14963"]"; line: 1, column: 1]

Is there not a way to do this with only annotations?


P.S. (rant)

I'd just like to add, this is an absurd data structure. It makes no sense. The purpose of using array indices instead of object field names would be to reduce the size of messages. This is a financial data stream, and any improvement to the network latency of financial data is desirable. But, around each of these arrays is a completely superfluous wrapper object with a single named field. This adds at least 10 bytes of unnecessary traffic per entry. The data structure has a very poor design.

William Rosenbloom
  • 2,506
  • 1
  • 14
  • 37

1 Answers1

1

There is more than one way to do this but I always prefer the combination of a dedicated class with a dedicated serializer. Other options would be:

  • registering the serializer with ObjectMapper – explicit coding instead of (meta level) annotations
  • use a general set method (with Map<String, Object>) – hides the serializer's aspect in a lengthy method
  • mapping JsonNode with setter in parent class – like #2

Instead:

@JsonDeserialize(using = OrderBookEntryDeserializer.class)
public class OrderBookEntry {
  // no further Jackson-annotations necessary
}

And the serializer:

class OrderBookEntryDeserializer extends StdDeserializer<OrderBookEntry> {
    OrderBookEntryDeserializer() {
        super(OrderBookEntry.class);
    }

    @Override
    public OrderBookEntry deserialize(JsonParser p, DeserializationContext ctxt)
            throws IOException, JsonProcessingException {
        final JsonNode node = p.getCodec().readTree(p);
        // now follow Jackson API to turn the node into
        // an ArrayNode, there are plenty of is-methods
        // and it requires a hard cast.
    }

My example uses a package private deserializer class which works absolutely fine and locks it in its package (preferrably next to the OrderBookEntry).

motzmann
  • 151
  • 6
  • Is there a way to do this without a custom deserializer? Ideally with annotations only? – William Rosenbloom Nov 14 '20 at 22:22
  • I think there is none. Presumable the average programmer simply maps this schema into an array and transform it with pure Java. This sort of mapping array index to attribute is very unusual – you already pointed that out in your post scriptum. (Which I truly appreciate avoiding all suggestions to change the schema instead). – motzmann Nov 15 '20 at 13:37