I'm using an java API that I do not own and cannot modify. That API defines a class Aggregation which is a Immutables (immutables.github.io) abstract class with Jackson serialization and deserialization.
The class looks like
#Value.Immutable
#JsonSerialize(as = Immutable_Aggregation.class)
#JsonDeserialize(as = Immutable_Aggregation.class)
public abstract class Aggregation {
I want to add a field to Aggregation which should be picked up at serialization time, but instances of which can still be passed to methods accepting Aggregation.
I want to do something like:
public abstract class ExtendedAggregation extends Aggregation {}
Is this possible?
I haven't managed to inherit between two classes marked with #Value.Immutable as you would like, so I tried to use Jackson to solve the issue instead.
Found the following approach, which uses the #JsonAppend annotation and mixins to add properties without touching the Aggregation class.
First, define a mix-in class with the annotation:
#JsonAppend(
attrs = {
#JsonAppend.Attr(value = "version")
})
public class AggregationMixin {}
Register the Mixin with your ObjectMapper instance:
mapper.addMixIn(Aggregation.class, AggregationMixin.class);
Serialize the class by creating a dedicated ObjectWriter, in which you specify the value of the added property:
ObjectWriter writer = om.writerFor(Aggregation.class).withAttribute("version", "42");
...
String output = writer.writeValueAsString(aggregationInstance);
The #JsonAppend annotation can be configured to obtain its value in a multitude of ways. Consult its documentation for details.
Some further reading :
http://www.baeldung.com/jackson-advanced-annotations#jsonappend
http://wiki.fasterxml.com/JacksonMixInAnnotations
Related
Consider we are having the following classes
//following classes are present in external lib, we can not modify them.
class A{
private Map mapOfListOfB; // this should have been properly typed Map<String,List<B>>
}
class B{
private int val1;
private String val2;
private C val3; // Class C can be anything but the point here is the same object of C can be used in multiple B objects
//which means we can reuse the reference using #JsonIdentityInfo
}
now, when we use Jackson's objectMapper to serialize and deserialize this Class A,
we would not be able to deserialize because we are not giving any typed info to Jackson so it ends up creating List<LinkedHashMap>
here is one solution that I know, works for class having a collection with a specific class type
eg:
class D{
private Map mapOfB // which should have been Map<String,B>
}
// this can be typed by using jackson's mixin
abstract class DmixIn{
#JsonDeserialize(contentAs = B.class)
Map mapOfB
}
but how we can tell the type which is present in class A to Jackson as we can not pass ParameterizedType to contentAs, it just takes class instance.
I know we can write a custom deserializer to achieve the end result but I am looking for a more readable solution here, like any Jackson annotation or any simple config which we can be set on the property level.
and another issue(which can be because of my awareness) is I even need to maintain deserialization context while writing custom deserializers by using #JsonDeserialize(contentUsing = CustomDeserializer.class) because Class C references are reused and I may need to resolve IDs for this POJO while deserializing
For polymorphic deserialisation, Jackson's ObjectMapper wants to know:
Which Subtypes are there to consider
How to decide, which Subtype to use
There are some standard ways, using fully qualified class names and certain reserved JSON properties, so Jackson can deduct those things without further configuration.
Another common way is, to provice Jackson with the necessary infromation by adding the Annotations #JsonTypeInfo and #JsonSubtypes to the base type. This however implies, that the file, declaring the base class has to be modified, when ever a new subtype is added.
It is also possible to regiser subtypes to the ObjectMapper programmatically at runtime via objectMapper.registerSubtypes(...).
Now I am looking for a way to also provide the information from #JsonTypeInfo programmatically at runtime without using that annotation.
Somthing like objectMapper.addTypeInfo(new TypeInfo(BaseType.class, PROPERTY, "myPropertyName", NAME); so I can use polymorphic deserialization on types that are declared in another project, that knows nothing of Jackson or any of its annotations.
To register the #JsonTypeInfo without modifying the actual class you have to use a mixin like this:
// actual base type that we don't want to or can't modfiy
// because it is in a different module / 3rd party
public class BaseType {
...
}
// mixin for BaseType to define #JsonTypeInfo
// this can be in a completely different package / module
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.PROPERTY, property = "type")
public abstract class BaseTypeMixIn {
}
The mixin must be registered manually for the ObjectMapper:
objectMapper.addMixIn(BaseType.class, BaseTypeMixIn.class);
BaseType now effectively has the #JsonTypeInfo from the mixin.
Mixin in jackson are the general solution to the problem "How do I annotate a class that I can't modify".
For sub-types the type info can be registered with ObjectMapper.registerSubtypes or by annotating the mixin with #JsonSubtypes. I prefer doing in without annotation in this case because it also works if different modules have different sub types of the base type. Registering multiple mixins will most likely not work.
public class SubTypeA extends BaseType {
...
}
public class SubTypeB extends BaseType {
...
}
Register in the ObjectMapper:
objectMapper.registerSubtypes(
new NamedType(SubTypeA.class, "A"),
new NamedType(SubTypeB.class, "B"));
I have a wrapper class WrapperClassthat encapsulates two objects: ObjectOne and ObjectTwo:
public class WrapperClass {
ObjectOne objectOne;
ObjectTwo objectTwo;
}
While serializing a WrapperClassinstance, I want to use the JaxbAnnotationIntrospector on objectTwo but not on objectOne. Is that possible?
I have looked into creating a custom annotation introspector that extends JacksonAnnotationIntrospector and overrides its findSerializer() method to pick JAXB's findSerializer() if the class type is ObjectTwo.class. Would that work?
Thank you.
As I mentioned in my other comment. What I am trying to achieve is not currently formally supported by the Jackson lib. A customized AnnotationIntrospectorPair that properly delegates the introspection is an idea to how to go about this. I will look into this more in the future.
I'm trying to create a generic class in android using Java but I want to make sure that the type would take specific classes with specific annotations like this:
I have class Table with annotation #Entity
#Entity
public class Table{}
and the generic class should only accept objects that have the #Entity annotation
Simple answer: not possible.
Annotations represent meta information that isn't available when constructing classes using generic type parameters.
if you want to check class annotation you have to use Reflection
http://static.javadoc.io/org.reflections/reflections/0.9.10/org/reflections/Reflections.html
getTypesAnnotatedWith is the function you need
final Reflections reflections = new Reflections(packagePrefix);
final Set<Class<?>> namedClasses = reflections.getTypesAnnotatedWith(Named.class);
for (final Class<?> namedClass : namedClasses) {
}
I am using jackson to handle JSON (de)-serialization. I have a bunch of classes which are annotated with are essentially objects holding properties and associated getters and setters.
However, I often find that at some point I want to add additional properties which I don't want to include in the (de)-serialization process. This does work using #JsonIgnore, but it strikes me as relatively ugly since I have to add the annotation everywhere and things break down as soon as I forget.
I would like to know if there is a better way to separate the ignored and serialized properties. I have the following two ideas:
Use inheritance, add the new properties to the inherited class:
// everything here should be (de)-serialized
class Base {
public int getJSONProperty() {...}
}
// nothing specific to the class Derived should be (de)-serialized
class Derived extends Base {
// *not* to be included
public SomeClass getAdditionalProperty() {...}
}
However, I don't know how to tell jackson to deserialize the Derived
objects as Bases. Is this possible (Does jackson make guarantees
regarding (non)-polymorphic serialization of classes)?
Use MixIn annotations. This would require an additional abstract
class for each existing class. Also I am not sure whether this solves
the problem. Are getters which do not appear in the MixIn base class
ignored automatically or do I need to #JsonIgnore them manually?
I've seen that you don't like the previous solution i've provided, so I'm again here to provide another way to do what you want using Gson Library. I hope to help you this time.
This is The Base Class that you want to serialize
public class Base {
public int getJSONProperty() {
return jsonProperty;
}
private int jsonProperty = 2;
}
This is The Derived Class that you don't want to serialize
public class Derived extends Base{
public String getAdditionalProperty(){
return additionalProperty;
}
private String additionalProperty = "value-not-to-serialize";
}
Using Type type = new TypeToken<Base>(){}.getType(); you can define the class to use for serialization so you get the JSON String using:
Derived derived = new Derived();
Gson gson = new Gson();
Type type = new TypeToken<Base>(){}.getType();
String jsonString = gson.toJson(derived, type);
You know Gson? it's a good library to handle JSON.
You can use transient keyword to define variable thats not must be serialized, this works with Gson (It should work well with jackson, but i'm not sure)...
class Base {
// (de)-serialized
private int jsonProperty;
// not (de)-serialized
private transient SomeClass additionalProperty;
}
I think the best approach is to add annotation or use the transient variable.
Create the inheritance only for the purpose of serialization an object complicates the application uselessly in my point of view...