Jackson deserialize generic class, custom optional - java

I have a type called OptionalField<T> which can be either empty(), defined(value) (like a normal optional) or nullValue(default) (represent a default value in JSON that could be anything like "n/a" or such).
My problem is with Jackson, because I need to create a custom serializer, and deserialiser capable of identifying the generic type and finding the appropriate default serializer, deserializer. I had not much trouble creating the serializer with a Contextual interface since it provides a property and Serializer provider
public JsonSerializer<?> createContextual(SerializerProvider prov, BeanProperty property) throws JsonMappingException {
...
JsonSerializer<Object> serialiser = prov.findValueSerializer(property.getType().containedType(0).getRawClass(), property);
...
}
But the deserializer is something else, I cannot find a way to identify the type and and find the default deserializer. Do you know how I could do that ? I read some things about BeanDeserializers but I don't think it answers to my problem.
Update
I went through the library https://github.com/FasterXML/jackson-datatype-jdk8 to understand how they do it and it seems very low level manipulation (types and scala). Is there really no other way to do that ? Some higher way to use Jackson ? It really seems like a common use case to me (generic classes I mean) ?

Using the type parameters in a serializer/deserializer isn't all that common, in my experience. It's more complicated than it could be, due in part to how type parameters were patched on to Java from version 1.5. This makes it difficult to get right in anything but the simple cases. Even the serializer example you give looks like it would only work for unparameterised classes, since it's finding a serializer based only on the raw T (what if you had OptionalField<Set<SomeObject>> ?)
The jdk8 or guava datatype add-ons are the place to start. Yes, they may seem daunting, but that complexity is (mostly) there for a reason. I've adapted them to support serializing/deserializing some other Optional-like type myself (fj.Option), so it's certainly possible.
When you implement Deserializers, the findBeanDeserializer method is called with the context of where the property was defined- so here you should have the full type with all its parameters:
#Override
public JsonDeserializer<?> findBeanDeserializer(JavaType type, DeserializationConfig config, BeanDescription beanDesc)
throws JsonMappingException {
Class<?> raw = type.getRawClass();
if (raw == Option.class) {
JavaType[] types = config.getTypeFactory().findTypeParameters(type, Option.class);
JavaType refType = (types == null) ? TypeFactory.unknownType() : types[0];
JsonDeserializer<?> valueDeser = type.getValueHandler();
TypeDeserializer typeDeser = type.getTypeHandler();
// [Issue#42]: Polymorphic types need type deserializer
if (typeDeser == null) {
typeDeser = config.findTypeDeserializer(refType);
}
return new FJOptionDeserializer(type, refType, typeDeser, valueDeser);
}
return super.findBeanDeserializer(type, config, beanDesc);
}
}
Here refType is the type of the parameter to Option, so we pass it to the constructor of the deserializer- i.e. each occurrence of an Option in classes analyzed by Jackson will get its own deserializer instance, customised with the parameters of the OptionalField type.
By and large, I copied the Optional implementation and it worked quite easily. The implementation of the OptionalDeserializer et al isn't the clearest (as it combines two cases in one class, imho confusingly).

Related

Jackson serialization failing when upgrading from 2.10 (InvalidDefinitionException: Type id handling not implemented for type java.lang.Object)

I'm upgrading from Jackson 2.10 to 2.12, and suddenly this simple test (which was working fine before) is now failing:
ObjectMapper mapper = new ObjectMapper();
mapper.enableDefaultTyping(DefaultTyping.NON_FINAL);
mapper.valueToTree(new org.joda.time.IllegalFieldValueException("testName", "testValue")); // causes error
java.lang.IllegalArgumentException: Type id handling not implemented for type java.lang.Object (by serializer of type com.fasterxml.jackson.databind.ser.impl.UnsupportedTypeSerializer)
at com.fasterxml.jackson.databind.ObjectMapper.valueToTree(ObjectMapper.java:3312)
at com.amazon.ets.util.exception.ExceptionSerializationTest.shouldSerializeException(ExceptionSerializationTest.java:77)
Caused by: com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Type id handling not implemented for type java.lang.Object (by serializer of type com.fasterxml.jackson.databind.ser.impl.UnsupportedTypeSerializer)
at com.fasterxml.jackson.databind.exc.InvalidDefinitionException.from(InvalidDefinitionException.java:77)
at com.fasterxml.jackson.databind.SerializerProvider.reportBadDefinition(SerializerProvider.java:1276)
at com.fasterxml.jackson.databind.DatabindContext.reportBadDefinition(DatabindContext.java:400)
at com.fasterxml.jackson.databind.JsonSerializer.serializeWithType(JsonSerializer.java:160)
at com.fasterxml.jackson.databind.ser.impl.TypeWrappedSerializer.serialize(TypeWrappedSerializer.java:32)
at com.fasterxml.jackson.databind.ser.DefaultSerializerProvider._serialize(DefaultSerializerProvider.java:480)
at com.fasterxml.jackson.databind.ser.DefaultSerializerProvider.serializeValue(DefaultSerializerProvider.java:319)
at com.fasterxml.jackson.databind.ObjectMapper.writeValue(ObjectMapper.java:3126)
at com.fasterxml.jackson.databind.ObjectMapper.valueToTree(ObjectMapper.java:3307)
... 24 more
I've gleaned from other similar posts like this one and this one that Jackson can struggle deserializing polymorphic types, but this is erroring on serialization not deserialization. Additionally, when I just try creating my own Exception subclass and try serializing it, it works just fine. I'm trying to use this as a general purpose serializer, so I don't want to have to manually add custom serializers for every object type -- I don't even know why IllegalFieldValueException in particular seems to be the only class that fails to serialize. So I have two main questions:
Why is this suddenly failing when I upgrade from Jackson 2.10 to a later version? I didn't change anything else! Is there a configuration option I can use to have it replicate the earlier version's behavior?
Why is IllegalFieldValueException the only type that seems to be failing to serialize? When I try serializing other exception subclasses or polymorphic types I don't see this error. What's so special about this particular class? (And are there any other classes that might cause the same behavior?)
The short answer is that Jackson basically broke this behavior with this commit: https://github.com/FasterXML/jackson-databind/commit/85c9c8544f0c4f01e88241acc1573746df4f755d
Ironically, there's actually a comment from one of the developers here (tatu) who asks whether they should add an override option allow force-POJO serialization: https://github.com/FasterXML/jackson-databind/blob/2.14/src/main/java/com/fasterxml/jackson/databind/ser/BeanSerializerFactory.java#L891
Unfortunately, this comment from tatu was apparently disregarded, because there's no way to override or disable this check. Even in the latest version of Jackson, it still has this same bad behavior.
The good news is that, as you can see in the implementation of the checkUnsupportedType() method, it only throws this error when attempting to serializing classes under the java.time or org.joda.time packages. That means you don't need to worry about getting this exception thrown when trying to serialize anything else. The bad news is that even if you add the JodaModule to your Jackson mapper, the JodaModule doesn't actually include exception types, so you'll still get this same error.
In the long term, the ideal fix would be for Jackson to add a configurable serialization option to force POJO serialization for time-related types, and/or for the JodaModule to be updated to include the exception types. But for now, you can fix this behavior by creating a subclass of the BeanSerializerFactory:
public class CustomBeanSerializerFactory extends BeanSerializerFactory {
public CustomBeanSerializerFactory(SerializerFactoryConfig config) {
super(config);
}
#Override
protected JsonSerializer<?> _findUnsupportedTypeSerializer(SerializerProvider ctxt, JavaType type, BeanDescription beanDesc) throws JsonMappingException {
return null;
}
#Override
public SerializerFactory withConfig(SerializerFactoryConfig config) {
if (_factoryConfig == config) return this;
return new CustomBeanSerializerFactory(config);
}
}
Then set your ObjectMapper to use this factory instead:
ObjectMapper mapper = new ObjectMapper();
mapper.setSerializerFactory(new CustomBeanSerializerFactory(null));
mapper.enableDefaultTyping(DefaultTyping.NON_FINAL);
mapper.valueToTree(new org.joda.time.IllegalFieldValueException("testName", "testValue")); // no error anymore - yay!

Make Jackson Subtypes extensible without editing the Supertypes java-file

In my company we have a fixed JSON message structure:
{
"headerVal1": ""
"headerVal2": ""
"customPayload": {
"payloadType":""
}
}
I would like to have some kind of library, which allows me, to not care for the company defined message structure, and instead just send and receive the payload.
My idea was, to define the structure of the company template as one object, and use subtypes of a PayloadObject.
#JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.MINIMAL_CLASS,
property = "payloadType",
visible = false)
public abstract class PayloadObject {
}
Now I can create subclasses of the PayloadObject, and it can be automatically deserialized in this structure, as long as the property payloadType has a string ".SubTypeName".
This is problematic, since I cannot customize it, not even remove the superflous . in the beginning. This is unfortunately not necessarily compatible with other, existing systems in the company, we need to interface with.
The alternative is, to add a #JsonSubTypes-annotation in which I can add all the possible subtypes - which I don't want to know when writing the library. So this option won't work for me.
I thought, it might help to have the #JsonType-annoation with the subtypes, but I still have to add the #JsonSubTypes, which does not help.
Is there a way, to add subtypes to a basetype without modifying the basetypes java-file?
If this helps: We are working with Java Spring.
ObjectMapper has a method registerSubtypes(NamedType) which can be used to add subtypes for use, without having them in the annotations.
For this I created a new Annotation (I might have reused #JsonTypeName, but it might be abusive)
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.TYPE)
public #interface MyJsonSubtype
{
public String jsonTypeName();
}
Then I wrote me a method
public static void registerMyJsonSubtypes(ObjectMapper om, Object... reflectionArgs) {
Reflections reflections = new Reflections(reflectionArgs);
Set<Class<?>> types = reflections.getTypesAnnotatedWith(MyJsonSubtype.class);
for (Class type : types) {
String name = ((MyJsonSubtype) type.getAnnotation(MyJsonSubtype.class)).jsonTypeName();
om.registerSubtypes(new NamedType(type, name));
}
}
which uses Reflections to get all annotated types declared inside searched packages and registers them as subtypes for the ObjectMapper.
This still requires the #JsonTypeInfo-annotation on the base class to mark the object as potentially extensible, so the mapper knows, which property to use, to resolve the name, but I figure, this is is providable.
My main attention was on the problem, that I don't want to declare all future subtypes in an annotation on the base class.
I am a Java beginner though, so please share your thoughts, if this is unnecessary or could/should/must be improved.

how to use method canDeserialize ObjectMapper class from jackson API?

I want to use method canDeserialize, because at moment deserialization i want to get type class for apply at custom deserialization, as about next example :
public T deserialize(byte[] bytes) throws SerializationException {
bolean isAccount = this.objectMapper.canDeserialize(??????).
T t = null;
if(isAccount)
t = (T)this.objectMapper.readValue(bytes,Account.class);
else
t = (T) this.objectMapper.readValue(bytes, 0, bytes.length, new TypeReference<Object>(){});
return t;
}
In this case Account class have annotation #JsonDeserialize for a custom deserialization .
To directly answer your question, this is how you use the canDeserialize method:
final ObjectMapper mapper = new ObjectMapper();
mapper.canDeserialize(mapper.constructType(Bean.class));
Where Bean is the name of your Java class to be checked.
But wait, you are trying to solve the wrong problem. You are struggling with the logic for your method because it has not been designed properly. You are really asking too much of the Java runtime (and Jackson library), by trying to make them infer all the required information about the type to be instantiated (based on the parameterized return). To solve this you should include the class representing the type to be deserialized as a parameter to the method, greatly simplifying the logic:
public <T> T deserialize(byte[] bytes, Class<T> clazz) throws IOException,
JsonProcessingException {
return new ObjectMapper().readValue(bytes, clazz);
}
At this point you have probably realized that the method above provides no additional functionality over just calling ObjectMapper.readValue directly, so ... just do that! No need to define custom methods, just use ObjectMapper and you are good to go. Keep in mind that you do not need to do anything explicit to trigger custom deserialization of classes. The Jackson runtime automatically detects when a class has a custom deserializer and invokes it.

Spring/json: Convert a typed collection like List<MyPojo>

I'm trying to marshal a list: List<Pojo> objects via the Spring Rest Template.
I can pass along simple Pojo objects, but I can't find any documentation that describes how to send a List<Pojo> objects.
Spring is using Jackson JSON to implement the HttpMessageConverter. The jackson documentation covers this:
In addition to binding to POJOs and
"simple" types, there is one
additional variant: that of binding to
generic (typed) containers. This case
requires special handling due to
so-called Type Erasure (used by Java
to implement generics in somewhat
backwards compatible way), which
prevents you from using something like
Collection<String>.class (which does
not compile).
So if you want to bind data into a
Map<String,User> you will need to use:
Map<String,User> result = mapper.readValue(src, new TypeReference<Map<String,User>>() {});
where TypeReference is only needed to
pass generic type definition (via
anynomous inner class in this case):
the important part is
<Map<String,User>> which defines type
to bind to.
Can this be accomplished in the Spring template? I took a glance at the code and it makes me thing not, but maybe I just don't know some trick.
Solution
The ultimate solution, thanks to the helpful answers below, was to not send a List, but rather send a single object which simply extends a List, such as: class PojoList extends ArrayList<Pojo>. Spring can successfully marshal this Object, and it accomplishes the same thing as sending a List<Pojo>, though it be a little less clean of a solution. I also posted a JIRA in spring for them to address this shortcoming in their HttpMessageConverter interface.
In Spring 3.2 there is now support for generic types using the new exchange()-methods on the RestTemplate:
ParameterizedTypeReference<List<MyBean>> typeRef = new ParameterizedTypeReference<List<MyBean>>() {};
ResponseEntity<List<MyBean>> response = template.exchange("http://example.com", HttpMethod.GET, null, typeRef);
Works like a charm!
One way to ensure that generic type parameters are included is to actually sub-class List or Map type, such that you have something like:
static class MyStringList extends ArrayList<String> { }
and return instance of that list.
So why does this make a difference? Because generic type information is retained in just a couple of places: method and field declarations, and super type declarations. So whereas "raw" List does NOT include any runtime type information, class definition of "MyStringList" does, through its supertype declarations.
Note that assignments to seemingly typed variables do not help: it just creates more compile-time syntactic sugar: real type information is only passed with Class instances (or lib-provided extensions thereof, like JavaType and TypeReference in Jackson's case).
Other than this, you would need to figure out how to pass Jackson either JavaType or TypeReference to accompany value.
If I read the docs for MappingJacksonHttpMessageConverter right, you will have to create and register a subclass of MappingJacksonHttpMessageConverter and override the getJavaType(Class<?>) method:
Returns the Jackson JavaType for the
specific class. Default implementation
returns
TypeFactory.type(java.lang.reflect.Type),
but this can be overridden in
subclasses, to allow for custom
generic collection handling. For
instance:
protected JavaType getJavaType(Class<?> clazz) {
if (List.class.isAssignableFrom(clazz)) {
return TypeFactory.collectionType(ArrayList.class, MyBean.class);
} else {
return super.getJavaType(clazz);
}
}
I have solved this problem by using the following configuration:
private static final String POJO_ARRAY_LIST = PojoArrayList.class.getCanonicalName();
#Bean
public HttpMessageConverter<Object> httpMessageConverter() {
HttpMessageConverter<Object> httpMessageConverter = new MappingJackson2HttpMessageConverter() {
#Override
protected JavaType getJavaType(Type type, #Nullable Class<?> contextClass) {
JavaType javaType;
if (type != null && POJO_ARRAY_LIST.equals(type.getTypeName())) {
ObjectMapper objectMapper = new ObjectMapper();
TypeFactory typeFactory = objectMapper.getTypeFactory();
CollectionType collectionType = typeFactory.constructCollectionType(ArrayList.class, Pojo.class);
javaType = collectionType;
} else {
javaType = super.getJavaType(type, contextClass);
}
return javaType;
}
};
return httpMessageConverter;
}
where PojoArrayList is a final class that extends ArrayList<Pojo>.

Jackson JSON library: how to instantiate a class that contains abstract fields

I want to convert a JSON string into java object, but the class of this object contains abstract fields, which Jackson can't instantiate, and doesn't produce the object. What is the easiest way to tell it about some default implementation of an abstract class, like
setDefault(AbstractAnimal.class, Cat.class);
or to decide about the implementation class based on JSON attribute name, eg. for JSON object:
{
...
cat: {...}
...
}
i would just wite:
setImpl("cat", Cat.class);
I know it's possible in Jackson to embed class information inside JSON, but I don't want to complicate the JSON format I use. I want to decide what class to use just by setting default implementation class, or by the attribute name ('cat') - like in XStream library, where you write:
xStream.alias("cat", Cat.class);
Is there a way to do so, especially in one line, or does it require some more code?
There are multiple ways; before version 1.8, simplest way is probably to do:
#JsonDeserialize(as=Cat.class)
public abstract class AbstractAnimal { ... }
as to deciding based on attribute, that is best done using #JsonTypeInfo, which does automatic embeddeding (when writing) and use of type information.
There are multiple kinds of type info (class name, logical type name), as well as inclusion mechanisms (as-included-property, as-wrapper-array, as-wrapper-object). This page: https://github.com/FasterXML/jackson-docs/wiki/JacksonPolymorphicDeserialization explains some of the concepts.
A full fledged answer with a very clear example can be found here: https://stackoverflow.com/a/30386694/584947
Jackson refers to this as Polymorphic Deserialization.
It definitely helped me with my issue. I had an abstract class that I was saving in a database and needed to unmarshal it to a concrete instance of a class (understandably).
It will show you how to properly annotate the parent abstract class and how to teach jackson how to pick among the available sub-class candidates at run-time when unmarshaling.
If you want to pollute neither your JSON with extra fields nor your classes with annotation, you can write a very simple module and deserializer that uses the default subclass you want. It is more than one line due to some boilerplate code, but it is still relatively simple.
class AnimalDeserializer extends StdDeserializer<Animal> {
public AnimalDeserializer() {
super(Animal.class);
}
public Animal deserialize(JsonParser jsonParser, DeserializationContext context) throws IOException {
return jsonParser.readValueAs(Cat.class);
}
}
class AnimalModule extends SimpleModule {
{
addDeserializer(Animal.class, new AnimalDeserializer());
}
}
Then register this module for the ObjectMapper and that's it (Zoo is the container class that has an Animal field).
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(new AnimalModule());
return objectMapper.readValue(json, Zoo.class);
The problem can be solved with the annotation #JsonDeserialize on the abstract class.
Refers to Jackson Exceptions Problems and Solutions for more info

Categories

Resources