Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Jackson catch unrecognized field in a map

I'm using Jackson in a java Rest Api to handle request params.

My Bean class :

public class ZoneModifBeanParam extends ModifBeanParam<Zone> {
@FormParam("type")
private String type;

@FormParam("geometry")
private Geometry geometry;

@FormParam("name")
private String name;

...

My API interface :

@POST
@Consumes("application/json")
@Produces("application/json; subtype=geojson")
@ApiOperation(value = "Create a zone", notes = "To create a zone")
public Response createZone(ZoneModifBeanParam zoneParam) {

...

This Works fine but I need to receive other params that aren't specified by my Bean in a Map. Example :

{
   "geometry": {...},
   "name": "A circle name",
   "type": "4",
   "hello": true
}

By receiving this I need to store in a Map (named unrecognizedFields and declared in my bean) the couple ("hello", true).

Is there any annotation or object allowing this?

like image 545
Guinoutortue Avatar asked Dec 19 '22 07:12

Guinoutortue


2 Answers

Just use @JsonAnySetter. That's what it's made for. Here is a test case

public class JacksonTest {

    public static class Bean {
        private String name;
        public String getName() { return this.name; }
        public void setName(String name) { this.name = name; }

        private Map<String, Object> unrecognizedFields = new HashMap<>();

        @JsonAnyGetter
        public Map<String, Object> getUnrecognizedFields() {
            return this.unrecognizedFields;
        }

        @JsonAnySetter
        public void setUnrecognizedFields(String key, Object value) {
            this.unrecognizedFields.put(key, value);
        }
    }

    private final String json
            = "{\"name\":\"paul\",\"age\":600,\"nickname\":\"peeskillet\"}";
    private final ObjectMapper mapper = new ObjectMapper();

    @Test
    public void testDeserialization() throws Exception {
        final Bean bean = mapper.readValue(json, Bean.class);
        final Map<String, Object> unrecognizedFields = bean.getUnrecognizedFields();

        assertEquals("paul", bean.getName());
        assertEquals(600, unrecognizedFields.get("age"));
        assertEquals("peeskillet", unrecognizedFields.get("nickname"));
    }
}

The @JsonAnyGetter is used on the serialization side. When you serialize the bean, you will not see the unrecognizedFields in the JSON. Instead all the properties in the map will be serialized as top level properties in the JSON.

like image 119
Paul Samsotha Avatar answered Jan 08 '23 19:01

Paul Samsotha


You may be able to ignore the unrecognized fields safely by configuring the ObjectMapper, however to specifically put them as key-value pairs of a Map field, you'll need your own de-serializer.

Here's a (heavily simplified) example:

Given your POJO...

@JsonDeserialize(using=MyDeserializer.class)
class Foo {
    // no encapsulation for simplicity
    public String name;
    public int value;
    public Map<Object, Object> unrecognized;
}

... and your custom de-serializer...

class MyDeserializer extends JsonDeserializer<Foo> {
    @Override
    public Foo deserialize(JsonParser p, DeserializationContext ctxt)
            throws IOException, JsonProcessingException {
        // new return object
        Foo foo = new Foo();
        // setting unrecognized container
        Map<Object, Object> unrecognized = new HashMap<>();
        foo.unrecognized = unrecognized;
        // initializing parsing from root node
        JsonNode node = p.getCodec().readTree(p);
        // iterating node fields
        Iterator<Entry<String, JsonNode>> it = node.fields();
        while (it.hasNext()) {
            Entry<String, JsonNode> child = it.next();
            // assigning known fields
            switch (child.getKey()) {
                case "name": {
                    foo.name = child.getValue().asText();
                    break;
                }
                case "value": {
                    foo.value = child.getValue().asInt();
                    break;
                }
                // assigning unknown fields to map
                default: {
                    foo.unrecognized.put(child.getKey(), child.getValue());
                }
            }

        }
        return foo;
    }
}

Then, somewhere...

ObjectMapper om = new ObjectMapper();
Foo foo = om.readValue("{\"name\":\"foo\",\"value\":42,\"blah\":true}", Foo.class);
System.out.println(foo.unrecognized);

Output

{blah=true}
like image 25
Mena Avatar answered Jan 08 '23 18:01

Mena