Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Gracefully ignore unknown class in Jackson JSON deserialization with type info?

Using the jackson JSON library, I'm able to generate JSON with typed field info and read it back. However, some clients of my JSON object may not have access to the particular class. In which I want them to just serialize the JSON attribute as a Map. That doesn't seem to work as demonstrated by the following codes. How to do this trick?

static class Foo {
    @JsonProperty
    private String x;
    @JsonProperty
    @JsonTypeInfo(use= JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY)
    private Object o;

    public String getX() {
        return x;
    }

    public void setX(String x) {
        this.x = x;
    }

    public Object getO() {
        return o;
    }

    public void setO(Object o) {
        this.o = o;
    }
}

@Test
public void testJacksonTypedSerialization() throws Exception {
    Foo foo = new Foo();
    foo.setX("hello world!");
    Foo foo2 = new Foo();
    foo2.setX("inner");
    foo.setO(foo2);

    ObjectMapper mapper = new ObjectMapper();

    String str = mapper.writeValueAsString(foo);
    System.out.println("foo is written as " + str);
    assertTrue(str.contains("Foo"));
    assertTrue(str.contains("@class"));

    Foo read = mapper.readValue(str, Foo.class);
    assertEquals(read.getX(), foo.getX());
    assertNotNull(read.getO());
    assertTrue(read.getO() instanceof Foo);

    String str2 = str.replace("Foo", "NoSuchType");
    Foo read2 = mapper.readValue(str2, Foo.class);
    assertEquals(read2.getX(), foo.getX());
    assertNotNull(read2.getO());
    // in this case, I want Jackson to read 'o' as Java Map/List
    assertTrue(read.getO() instanceof Map);
    // !!! encountered error
    //com.fasterxml.jackson.databind.JsonMappingException: Invalid type id 'com.mycorp.NoSuchType' (for id type 'Id.class'): no such class found
}
like image 421
teddy Avatar asked May 27 '15 00:05

teddy


People also ask

How do I ignore unknown properties on Jackson?

Jackson API provides two ways to ignore unknown fields, first at the class level using @JsonIgnoreProperties annotation and second at the ObjectMapper level using configure() method.

How do you tell Jackson to ignore a field during serialization?

If there are fields in Java objects that do not wish to be serialized, we can use the @JsonIgnore annotation in the Jackson library. The @JsonIgnore can be used at the field level, for ignoring fields during the serialization and deserialization.

How do I ignore a JSON property?

To ignore individual properties, use the [JsonIgnore] attribute. You can specify conditional exclusion by setting the [JsonIgnore] attribute's Condition property. The JsonIgnoreCondition enum provides the following options: Always - The property is always ignored.


1 Answers

Well, I think you can get progressively closer solutions as you put more effort in. In theory, you should be able to write a custom type resolver that returns unknown if the class does not exist (the default Id.CLASS resolver never does this). This quickly turned into something deeper than I had time to go into when I tried it, though.

A simpler solution is to configure the standard name resolver (which can return "type unknown") and specify a defaultImpl. Something like:

    @JsonProperty
    @JsonTypeInfo(defaultImpl=JsonNode.class, use= JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY)
    @JsonSubTypes({ @JsonSubTypes.Type(name="foo-type", value=Foo.class) })
    public Object o;

One thing I found is that it you specify defaultImpl as Map, then it doesn't serialize as a map, but tries to treat the Map class as a bean (and hence finds no properties). You could either keep hold of the JsonNode or use the object mapper to convert it on to a Map properly.

I'm not actually sure that kind of swapping is possible- once you're into deserializing in a type hierarchy, you might already be committed to deserializing as a bean? In which case you're heading into custom deserializer territory.

I tweaked the test a bit to get it passing with those annotations on 'o':

@Test
public void testJacksonTypedSerialization() throws Exception {
    Foo foo = new Foo();
    foo.setX("hello world!");
    Foo foo2 = new Foo();
    foo2.setX("inner");
    foo.setO(foo2);

    String str = mapper.writeValueAsString(foo);
    System.out.println("foo is written as " + str);
    assertThat(str, both(containsString("foo-type")).and(containsString("@type")));

    Foo read = mapper.readValue(str, Foo.class);
    assertThat(read.getX(), equalTo(foo.getX()));
    assertThat(read.getO(), instanceOf(Foo.class));

    String str2 = str.replace("foo-type", "NoSuchType");
    Foo read2 = mapper.readValue(str2, Foo.class);
    assertThat(read2.getX(), equalTo(foo.getX()));
    assertThat(read2.getO(), instanceOf(ObjectNode.class));
}
like image 179
araqnid Avatar answered Oct 06 '22 00:10

araqnid