8

I am trying to write a custom deserializer in order to trim down a big set of data I receive from somewhere else. I return a List of custom objects from the deserializer.

My question is, how do I do that, if this is my custom deserializer :

public class MyCustomDeserializer extends JsonDeserializer<List<CustomClass>> { ... }

I certainly can't do this :

final SimpleModule module = new SimpleModule();
module.addDeserializer(List<CustomClass>.class, new MyCustomDeserializer());

Will something like this work ?

final List<CustomClass> response = Arrays.asList(objectMapper.readValue(stringBean, CustomClass[].class));

If this indeed works, I find it a bit confusing and "dangerous" ? Isn't the deserialization done inside the asList method invocation ? So it basically maps a List to an array[] ?

I learned about TypeReference so I can probably use that like so :

objectMapper.readValue(stringBean, new TypeReference<List<CustomClass>>(){});

but I heard it is slower.

I also don't want to create a container for the list, and return that in the deserialization because that means it will be wrapped in another json object, and I simply want my endpoint to produce something like :

[{object1}, {object2}]

// instead of

{"Output" : [{object1}, {object2}]}

EDIT:

It seems that I have misinterpreted how jackson is using my deserializer in both cases :

final List<CustomClass> response = Arrays.asList(objectMapper.readValue(stringBean, CustomClass[].class));
// or
objectMapper.readValue(stringBean, new TypeReference<List<CustomClass>>(){});

It looks like the deserializer is called twice, once for each object in the array. I thought that the entire array would be considered as a whole. To clear the confusion, here is what I mean:

The json I receive and try to deserialize looks like so :

[
  {
    "Data" : {
      "id" : "someId",
      "otherThing" : "someOtherThing"
    },
    "Message" : "OK"
  },
  {
    "Data" : null,
    "Message" : "Object not found for id blabla"
  }
]

and so I though this is what I would have inside my deserializer, but as I said before it seems that i actually get each "entry" from that array and call it multiple times.

D.Razvan
  • 325
  • 1
  • 5
  • 18
  • Also need to state that i registered the deserializer on the bean using the @JsonDeserializer annotation. – D.Razvan Dec 09 '15 at 10:59
  • 1
    Use the `ObjectMapper`'s `TypeFactory`; it allows you to create type descriptions like you want, ie, "a List of CustomClass". Also, please mention the version of Jackson you are using. – fge Dec 09 '15 at 11:52
  • 1
    I'm using jackson 2.4 version. Can you provide me with some materials in using the type factory method ? Thanks. – D.Razvan Dec 09 '15 at 12:27

3 Answers3

17

First of all, If you registered your custom deserializer using annotation on the bean CustomClass then the deserializer should handle one instance of CustomClass and not a collection and thus should be defined:

public class MyCustomDeserializer extends JsonDeserializer<CustomClass> {
        @Override
        public CustomClass deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException
        {
            ...
        }
}

and now you can use Jackson's type factory to pass the mapper the required type information

JavaType customClassCollection = objectMapper.getTypeFactory().constructCollectionType(List.class, CustomClass.class);
List<CustomClass> beanList = (List<CustomClass>)objectMapper.readValue(stringBean, customClassCollection);
Sharon Ben Asher
  • 13,849
  • 5
  • 33
  • 47
  • I figured it out, and then I saw your answer. I will accept this as the correct answer since It's actually what I was trying to do. – D.Razvan Dec 09 '15 at 14:32
  • Excellent example of a custom deserializer, and how to manipulate it to deserialize a list of JSON objects. – Andrew Carr May 26 '19 at 04:35
10

I worked it out by adding a custom deserializer to an attribute in my model class and using JsonDeserialize annotation's contentUsing() method, like so:

@JsonDeserialize(contentUsing = MyCustomDeserializer.class)
private List<CustomClass> customClassObjList;

where MyCustomDeserializer class is a custom Jackson JSON deserializer defined as:

public class MyCustomDeserializer extends JsonDeserializer<CustomClass> {
    
    @Override
    public CustomClass deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
        ...
    }
}
Vivek Sethi
  • 894
  • 2
  • 13
  • 20
  • Does it mean Jackson can automatically deserialize List even though you have only defined the deserialize for the individual CustomClass object? – user2441441 Mar 15 '21 at 02:27
  • 6
    Yes. The trick is to use `contentUsing` not `using` – Bobby T Jakachira Jun 10 '21 at 14:13
  • how do I use `contentusing` for a custom deserializer which is registered via SimpleModule `SimpleModule module = new SimpleModule();` `module.addDeserializer(MyCustomDeserializer.class, CustomClass);` `ObjectMapper objectMapper = new ObjectMapper();` `objectMapper.registerModule(module);` – SatheeshJM Jun 09 '23 at 08:18
2

These two lines will just do enough.

ArrayNode arrayNode = (ArrayNode) objectMapper.readTree(stringBean);
List<CustomClass> response = objectMapper.convertValue(arrayNode, List.class); 

Thank me later!