Custom deserializer for generic list - json

I'm trying to create a custom deserializer for generic lists. Lets say I get a json representation of class B:
public class B{
List<A> listObject;
}
where A is some other class which I see only at runtime. I'd like to create a deserializer that will be able to infer the type of listObject as list with inner type A and deserialize it as such instead of using the default hashmap deserializer.
I tried using contextual deserializer, similar to what was suggested here
and then adding it as a custom deserializer for List
addDeserializer(List.class, new CustomListDeserializer())
But I'm not sure how am I supposed to read the json and create the list in deserialize function (in the Wrapper example above it's pretty simple, you read the value and set it as a value field, but if my 'wrapper' is List, how do I read the values and add them?)
I tried using readValue with CollectionType constructed with constructCollectionType(List.class, valueType) but then I go into an infinite loop, since readValue uses the deserializer from which it was called.
Any ideas?

Thanks for the suggestion. I solved it by parsing the json as an array of inner generic type and then converting to list, as follows:
Class<?> classOfArray = Array.newInstance(valueType.getRawClass(), 0).getClass();
Object[] parsedArray = (Object[]) parser.getCodec().readValue(parser, classOfArray);
return Arrays.asList(parsedArray);

Related

Jackson Map Null Json to List

I've searched and found Jackson ObjectMapper throwing NullPointerException even with NON_NULL, but I don't have control of the class to change my setter.
I have am being given
{... "fieldNames": null,...}
and am supposed to deserialize it to
Collection<String> fieldNames
I don't have control of the class or the json I'm getting.
Is there some setting I can use to handle for this? I've looked at DeserializationFeature, but could not find it
You can use mix-ins when you don't control the class you are deserializing. You don't mention the name of the class containing Collection<String> fieldNames so lets assume it's called Fields. Then create a new class:
class FieldsMixin {
#JsonSetter(nulls = Nulls.SKIP)
Collection<String> fieldNames;
}
and add the mixin class to your ObjectMapper associating it with the original unmodified class:
mapper.addMixIn(Fields.class, FieldsMixin.class);
This is a new feature in Jackson 2.9 and as you guess it will skip calling a setter method or otherwise set a field if the value in JSON is null. Documentation

Why I parse json into a Java List, but not a Scala List?

I am attempting to parse a json object that contains a list. I am able to parse the list if the field is backed by a Java List, but it fails if the field is backed by a Scala list. What is the difference between parsing into a Scala List vs a Java List, and what do I have to change to be able to parse this into a Scala List?
object JsonParsingExample extends App {
val objectMapper = new ObjectMapper()
// This line succeeds.
objectMapper.readValue("""{"list": ["a","b"]}""", classOf[JavaList])
// This line fails.
objectMapper.readValue("""{"list": ["a","b"]}""", classOf[ScalaList])
}
case class JavaList() {
#JsonProperty(value = "list")
var myList: java.util.ArrayList[String] = null
}
case class ScalaList() {
#JsonProperty(value = "list")
var myList: List[String] = null
}
The error message I receive is:
com.fasterxml.jackson.databind.JsonMappingException: Can not construct instance of scala.collection.immutable.List, problem: abstract types either need to be mapped to concrete types, have custom deserializer, or be instantiated with additional type information
Jackson doesn't know anything about Scala types by default (otherwise it would have to depend on scala-library). To teach it, use jackson-module-scala.
Because the scala.collection.immutable.List is actually an abstract class. Generally when you use List("a", "b", "c") is the object List.apply() which is coming from this line: https://github.com/scala/scala/blob/2.12.x/src/library/scala/collection/immutable/List.scala#L452 and that's actually an inner class (something called scala.collection.immutable.$colon$colon).

Access deserialized object from custom deserializer

All classes in my model are subclasses of a common base-class Node.
When serializing/deserializing with Jackson, I want references to other objects to be replaced by their ID. The problem is, that the ID is a combination of two values: the ID of the instance itself, and the ID of a source. I do this with a custom serializer and deserializer.
Serializing is no problem. I write JSON with a structure like this:
{"id":1,"source":2,"name":"Some record","reference":3}
But when deserilizing, I need to know the ID of the source and the ID of the referenced node, to be able to look it up in my custom deserializer.
Is it possible, to access the values of the deserialized instance, to get access to the ID of the source when deserializing the reference?
Here is what I tried so far:
public class MyDeserializer extends JsonDeserializer<Node>
{
#Override
public NodeData deserialize(...)
{
Node parent = (Node)parser.getCurrentValue();
Long id = parent.getId();
Long id = parser.getLongValue();
return NodeDataService.INSTANCE.get(source, id);
}
}
But parser.getCurrentValue() always returns a null.
My best solution so far is, to write a cooperation pair of deserializers.
The first one is annotated to the getter of the attribute source and stores
the value as per-call attribute. The second looks like this:
public class MyDeserializer extends JsonDeserializer<Node>
{
#Override
public NodeData deserialize(...)
{
Long source (Long)context.getAttribute("SOURCE");
Long id = parser.getLongValue();
return NodeDataService.INSTANCE.get(source, id);
}
}
This works, but I am asking myself, if there is an easier way to achieve this.
This question looks like it is possible, like I did it in my first attempt - but only while serializing:
Jackson How to retrieve parent bean in a custom Serializer/Deserializer
getCurrentValue() will return null as you've entered into a new JSON object but not yet set the current value. You need to look at the stack of deserialized values in the parser context.
I answered something similar here, which is the deserialization equivalent of the serialization question you linked to: Jackson JSON gives exception on collection of nested class
In summary you can get the stream context:
JsonStreamContext ourContext = p.getParsingContext();
and then repeatedly call getParent() on contexts to walk up the chain, calling getCurrentValue(). The value is set into the stream context as soon as the standard bean deserializer constructs the object.

Map JSON array of objects to #RequestBody List<T> using jackson

I'm having issues using Jackson to map a Javascript posted JSON array of hashes (Tag).
Here is the data received by the controller #RequestBody (It is send with correct json requestheader):
[{name=tag1}, {name=tag2}, {name=tag3}]
Here is the controller:
#RequestMapping(value = "purchases/{purchaseId}/tags", method = RequestMethod.POST, params = "manyTags")
#ResponseStatus(HttpStatus.CREATED)
public void createAll(#PathVariable("purchaseId") final Long purchaseId, #RequestBody final List<Tag> entities)
{
Purchase purchase = purchaseService.getById(purchaseId);
Set<Tag> tags = purchase.getTags();
purchaseService.updatePurchase(purchase);
}
When I debug and view the 'entities' value it shows as an ArrayList of generic objects, not as a list of objects of type 'Tag' as I would expect.
How can I get jackson to map a passed array of objects to a list of obejcts of type 'Tag'?
Thanks
It sounds like Spring is not passing full type information for some reason, but rather a type-erased version, as if declaration was something like List<?> tag. I don't know what can be done to fully resolve this (may need something from Spring integration team), but one work-around is to define your own type like:
static class TagList extends ArrayList<Tag> { }
and use that instead. This will retain generic parameterization through super-type declarations so that even if Spring only passes equivalent of TagList.class, Jackson can figure out the Tag parameter.
Another way to do this is to rather obtain an array than a List, as follows:
#RequestBody Tag[] entities
Jackson requires a default constructor with no parameters on custom Objects, so you'll need to simply add a default constructor to your Tag class.
In your case simply add to your Tag class:
public Tag(){}

Dynamically ignore properties with JacksonJson

I'm aware that there are multiple way to tell JacksonJson to ignore
properties during rendering but all of them are static. (JasonIgnore, MixIn classes, ..).
This is my scenario. A domain object can implement a interface called FilteredDomain to
allow it to be dynamically filtered. The interface is simple and only exposes one method
"getIgnoreProperties". (A list of properties to ignore).
I then register a Custom Serializer that binds to the FilteredDomain object. The
code looks something like:
private class FilteredDomainSerializer extends JsonSerializer<FilteredDomain> {
public void serialize(FilteredDomain arg, JsonGenerator jgen,
SerializerProvider provder) throws IOException,
JsonProcessingException {
final BeanWrapper wrapper = PropertyAccessorFactory.forBeanPropertyAccess(arg);
for (PropertyDescriptor pd : wrapper.getPropertyDescriptors()) {
final String name = pd.getName();
if (arg.getIgnoreProperties().containsKey(name))
continue;
final Object value = wrapper.getPropertyValue(name);
jgen.writeObjectField(name, value);
}
}
}
First, I really dislike that I need to use the Spring Bean wrapper to get a list of all properties and iterate through them (There must be a way to do this is jackson json).
Second, The code still dosen't work. I get the error:
org.codehaus.jackson.JsonGenerationException: Can not write a field name, expecting a value
at org.codehaus.jackson.impl.JsonGeneratorBase._reportError(JsonGeneratorBase.java:480)
at org.codehaus.jackson.impl.Utf8Generator.writeFieldName(Utf8Generator.java:270)
at org.codehaus.jackson.JsonGenerator.writeObjectField(JsonGenerator.java:1088)
at com.rootmusic.util.SystemJsonObjectMapper$ValueObjectSerializer.serialize(SystemJsonObjectMapper.java:65)
at com.rootmusic.util.SystemJsonObjectMapper$ValueObjectSerializer.serialize(SystemJsonObjectMapper.java:1)
at org.codehaus.jackson.map.ser.ContainerSerializers$IndexedListSerializer.serializeContents(ContainerSerializers.java:304)
at org.codehaus.jackson.map.ser.ContainerSerializers$IndexedListSerializer.serializeContents(ContainerSerializers.java:254)
at org.codehaus.jackson.map.ser.ContainerSerializers$AsArraySerializer.serialize(ContainerSerializers.java:142)
at org.codehaus.jackson.map.ser.MapSerializer.serializeFields(MapSerializer.java:287)
at org.codehaus.jackson.map.ser.MapSerializer.serialize(MapSerializer.java:212)
at org.codehaus.jackson.map.ser.MapSerializer.serialize(MapSerializer.java:23)
at org.codehaus.jackson.map.ser.StdSerializerProvider._serializeValue(StdSerializerProvider.java:606)
at org.codehaus.jackson.map.ser.StdSerializerProvider.serializeValue(StdSerializerProvider.java:280)
The error comes from the fact that you are not writing START_OBJECT / END_OBJECT around field-name/value pairs, so that should be easy to fix.
As to more dynamic filtering, you could read this blog entry which includes standard methods. #JsonView works if you have sets of static definitions (one of which you can dynamically select on per-serialization basis), but if you want yet more dynamic system, #JsonFilter is the way to go.
Alternatively, another relatively simple way would be to first "convert" your POJO into a Map:
Map props = objectMapper.convertValue(pojo, Map.class);
(which is similar to serializing it as JSON, except that result is a Map which would render as JSON)
and then selectively trim Map, and serialize that as JSON. Or, if you prefer, you can use JsonNode ("tree model") as the intermediate thing to modify and then serialize.