简体   繁体   中英

Deserialising different types to single field using Jackson

I have a following class that I want to use for deserializing JSON

public interface MyObject {

    @JsonProperty("prop")
    String prop;

    @JsonProperty("value")
    Double value();        // Need this to be either a double or a string or a Map
}

However, I want to be able to parse both the JSON with a double value

{
  prop: "myprop",
  value: 15.7
}

and a JSON with non Double value like a string or a map

{
  prop: "myprop1",
  value: {
    "attr1": "value1",
    "attr2": 12.0
  }
}

I looked at @JsonSubTypes annotation, but that looks like only useful for the cases where inheritance is involved. Is it possible to do it in Jackson? If so how can I define my Java class to achieve the same?

In general, I'd discourage the use of arbitrary types of data points. Having strong types gives plenty of benefits about which I can talk if you want. However, since you only talked about deserialization it might be that you are just reading such a JSON produced by someone else.

The solution is quite simply: use Object field.

public static class MyObject {

    @JsonProperty("prop")
    String prop;

    @JsonProperty("value")
    Object value;        // <- object
}

@Test
public void testUnknownType() throws JsonProcessingException {
    final ObjectMapper objectMapper = new ObjectMapper();
    final MyObject object1 = objectMapper.readValue("{\n" +
        "  \"prop\": \"myprop\",\n" +
        "  \"value\": 15.7\n" +
        "}", MyObject.class);
    Assert.assertEquals(15.7d, object1.value);
    final MyObject object2 = objectMapper.readValue("{\n" +
        "  \"prop\": \"myprop1\",\n" +
        "  \"value\": {\n" +
        "    \"attr1\": \"value1\",\n" +
        "    \"attr2\": 12.0\n" +
        "  }\n" +
        "}", MyObject.class);
    Assert.assertTrue(object2.value instanceof Map);
}

What you could is something like this:

@JsonDeserialize(using = ExampleDeserializer.class)
public class Example{
String prod;
Object value; /*this field will takes multiple types*/
}

and the ExampleDeserializer would be like this:

public class ExampleDeserializer extends StdDeserializer<Example> {

    public ExampleDeserializer() {
        super(Example.class);
    }

    public ExampleDeserializer(Class<?> vc) {
        super(vc);
    }

@Override
    public Example deserialize(JsonParser p, DeserializationContext ctx) throws IOException {
        Example ex = new Example();
        ObjectCodec mapper = p.getCodec();
        if (mapper == null) mapper = new ObjectMapper();
        JsonNode tree = mapper.readTree(p);
        JsonNode internalNode;

        if (tree.get("prod") != null) {
          internalNode = tree.get("prod");
          prop = internalNode.get("prod").asText();
        }

       if (tree.get("value") != null) {
          internalNode = tree.get("value");
          value = (Double) internalNode.get("value").asDouble() or asText()...;
        }

}

You'll have a slightly easier time of things if you have your different types as different names, so in JSON it should be:

{
  prop: "my_map_prop",
  mapvalue: {
    "attr1": "value1",
    "attr2": 12.0
  }
}

or

{
  prop: "my_string_prop",
  stringvalue: "string"
}

If you do it like this you then have more tools at your disposal to enforce validity.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM