1

In my JSON I have an element with the following contents:

{
    ...
    "locations": [
        [
            {
                "location_type": "permanent",
                "position": "at",
                "accuracy": "exact"
            },
            "and",
            {
                "location_type": "permanent",
                "position": "in",
                "accuracy": "exact"
            }
        ],
        "or",
        {
            "location_type": "temporary",
            "position": "at",
            "accuracy": "exact"
        }
    ],
    ...
}

As shown, an element of locations can be:

  • a location
  • a logical operator
  • a list of locations (allowing for complex locations)

I'm getting "Cannot deserialize instance of com.example.processor.transformation.json.Location out of START_ARRAY token".

How can I consume this into a data structure using Jackson?

What I tried so far:

  1. Providing a Location(String logicalOperator) constructor helps for a flat list case. (I basically turn the operator into a special value of Location.)
  2. Adding a Location(List<Location> subLocations) or a Location(Location[] subLocations) constructor doesn't help for this case.

Note: I am not in control of the JSON format so I cannot encode it in a more Jackson-friendly way.

AdSR
  • 625
  • 1
  • 6
  • 7
  • 1
    You're going to need a custom de-serializer for that. You can't just add a constructor. – Mena Apr 16 '18 at 09:12
  • @saifahmad Please review duplicate status. This is a different problem, with a different solution. – AdSR Apr 17 '18 at 09:41

1 Answers1

1

You're going to need a custom de-serializer for that. You can't just add a constructor.

Here's a self-contained example with class Foo, that can be either represented by its own property "foo" : "someString" or by some logical operator "and" or "or", etc. as a String literal, intended to represent a Foo instance whose foo property will be the value of that literal.

This may or may not fit your case exactly, but you can adjust.

In other words:

  • {"foo": "a"} --> new Foo("a")
  • "or" --> new Foo("or")

Example

// given...

@JsonDeserialize(using=MyDeserializer.class)
class Foo {
    String foo;
    public void setFoo(String s) {
        foo = s;
    }
    public String getFoo() {
        return foo;
    }
    public Foo(String s) {
        setFoo(s);
    }
}

// and custom de-serializer...

class MyDeserializer extends JsonDeserializer<Foo> {

    @Override
    public Foo deserialize(JsonParser jp, DeserializationContext ct)
            throws IOException, JsonProcessingException {
        ObjectCodec oc = jp.getCodec();
        JsonNode node = oc.readTree(jp);
        // this JSON object has a "foo" property, de-serialize 
        // injecting its value in Foo's constructor
        if (node.has("foo")) {
            return new Foo(node.get("foo").asText());
        }
        // other case, assuming literal (e.g. "and", "or", etc.)
        // inject actual node as String value into Foo's constructor
        else {
            return new Foo(node.asText());
        }
    }

}

// here's a quick example

String json = "[{\"foo\": \"a\"}, \"or\", {\"foo\": \"b\"}]";
ObjectMapper om = new ObjectMapper();
List<Foo> list = om.readValue(json, new TypeReference<List<Foo>>(){});
list.forEach(f -> System.out.println(f.foo));

Output

a
or
b

Note for clarity

This represents a very simple example. In your case, you're probably going to want a polymorphic collection of Location POJOs mixed with LogicalOperator POJOs (or something similar), sharing a common marker interface. You can then decide what object to de-serialize based on whether the JSON node features contents (i.e. a location) or the JSON node is its contents (e.g. the logical operators).

Mena
  • 47,782
  • 11
  • 87
  • 106
  • While this isn't the final solution I think you pointed me in the right direction. I tried applying `@JsonDeserialize` in various places with `Location` being an interface but that doesn't seem to even trigger the custom deserializer. – AdSR Apr 16 '18 at 12:52
  • @AdSR what do you mean by "in various places"? You only need to annotate before the class/interface declaration. If you have a common interface for your locations and logical operators, you can annotate before that interface's declaration. – Mena Apr 16 '18 at 12:54
  • Never mind, I was trying to use the wrong Jackson package and it didn't match the parser (`org.codehaus...` vs. `com.fasterxml...`). In the end the only difference between your solution and mine is that I dispatch on node type by `node.isArray()` etc. – AdSR Apr 17 '18 at 09:39
  • @AdSR a**HA**. Yes, the `org.codehaus` is pretty old now - reminds me of my own answer [here](https://stackoverflow.com/questions/30782706/org-codehaus-jackson-versus-com-fasterxml-jackson-core/30782762#30782762) :D You have plenty of options in your own de-serializer, in the end it boils down to which ever you find handier to deal with your full payload. – Mena Apr 17 '18 at 09:42