How to add derived properties to a Jackson 2 serialized class? - java

I'm serializing some existing objects with Jackson 2.22, leveragin the MixIn feature to decouple the real object from the Jackson annotations configuration.
Actually my mixin is an interface that declares the same methods of the target class and annotates them, here's an example.
Target class:
public class Product {
// ...
public String getName();
public String getDescription();
public String getPrice();
public String getFinalPrice();
public String getDiscount();
// ...
}
and the mixin:
public interface ProductApi {
#JsonProperty
public String getName();
#JsonProperty("price")
public String getFinalPrice();
}
My JSON should have some more informations, computed from several methods or fields of the target class.
Is this even possible in Jackson?
I tried turning the mixin in a class and adding a new method there, but that didn't work.
public class ProductApi {
#JsonProperty
public String getName();
#JsonProperty("price")
public String getFinalPrice();
#JsonProperty("images")
public List<String> getImages() { /* ... */ }
}
I guess this is because the mixin only provides annotations for the target class, but is the latter that is read for serialization.
Of course, if I change the object to be serialized with a new subclass that contains the new method I need, that works, but the objects come from our services layers, and this would mean I have to rewrite all those methods.
I'm using Jackson with Jersey, so don't want to change Jackson with another library.

Here's how I did it.
The solution is to specify a custom JsonSerializer implementation to the field getter.
First of all, I changed the mixin interface to a class that extends the entity (target) class, so that it can access the target class data.
public class ProductApi extends Product {
#JsonProperty
#Override
public String getName() {
return super.getName();
};
// ...
}
Next, I implemented the JsonSerializer that would create the derived property I want:
public static class ImagesSerializer extends JsonSerializer<String> {
#Override
public void serialize(String value, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonProcessingException {
Product p = (Product) jgen.getCurrentValue();
int num = p.getNumberOfImages();
List<String> imgs = new ArrayList<String>(num);
for(int i = 0; i < num; i++) {
String src = "/include/images/showImage.jsp?"+"id="+p.getId()+"&number="+i;
imgs.add(src);
}
provider.defaultSerializeValue(imgs, jgen);
}
}
This is a really simple implementation, more safety checks should be done.
What this does is, basically, retrieve the whole entity instance from the JSON generator, build up a custom object and then ask Jackson to serialize it.
I implemented it inside my ProductApi as a static class, but just for simplicity.
Finally, the serializer needs to be bound to the JsonProperty annotated field:
public class ProductApi extends Product {
#JsonProperty
#Override
public String getName() {
return super.getName();
};
// ...
#JsonSerialize(using=ImagesSerializer.class)
#JsonProperty("images")
#Override
public String getImage() { // in my entity this returns an image number, whereas in my JSON I want a list of URLs
return "";
}
// ...
}
As a side note, it seems that the returned value of the getImage() method is not used.

Why don't you just make some fields, which should be serialized and use Gson for it?

Related

Get type of property in deserializer (Jackson)

I'm looking for a way to deserialize a subclass using a deserializer registered using the #JsonDeserialize annotation on the abstract super class. If there are better options I'm happy to adapt these options – but I'm not aware of any solution to this problem at the moment.
The core problem is: There is an abstract super class A:
#JsonSerialize(using = SerializerForA.class)
#JsonDeserialize(using = DeserializerForA.class)
public abstract class A {
private String value;
protected A(String value) {
this.value = value;
}
...
}
(The annotations are my attempt to do custom deserialization – maybe it's the wrong approach).
There are some derived classes, and A doesn't know any of the derived classes. Think about A is part of a framework and the derived classes are client code using the framework. Here are two derived classes:
public class B extends A {
public B(String value) {
super(value);
}
...
}
and
public class C extends A {
public C(String value) {
super(value);
}
...
}
These derived classes are used in a "container" class, e.g.:
public class MyClass {
private B b;
private C c;
...
}
And the corresponding JSON looks like this:
{
"b": "value_of_b",
"c": "value_of_c"
}
Writing a serializer is relatively simple:
public class SerializerForA extends JsonSerializer<A> {
#Override
public void serialize(A obj, JsonGenerator gen, SerializerProvider serializers) throws IOException {
gen.writeString(obj.getValue());
}
}
The deserializer would look like this:
public class DeserializerForA extends JsonDeserializer<A> {
#Override
public A deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JacksonException {
A result = ???
return result;
}
}
But how does the deserializer know, which type the resulting object has? Is it possible to get the class name from one of the parameters (e.g. DeserializationContext)?
There are some ways the code can be changed, if it helps. For example, a setter can be used for the value field, instead of the constructor, but I would prefer a constructor, or some factory method (public static A getInstance(String value) { ... }).
Edit (1) The deserializer should be called without any specific code automatically by the ObjectMapper, like:
ObjectMapper mapper = new ObjectMapper();
MyClass myClass = mapper.readValue(json, MyClass.class);
That means, Jackson knows the type of the container class. It also knows the type of the properties a and b.
The reason to use a custom deserializer is that I need to have control over the instance creation process (basically, I want to reuse the same instance of each object for the same value – similar to an enum).
Edit (2) Changing the JSON structure is not an option. But I don't think it should be necessary. If I didn't need to have control over instance creation, the whole implementation would just work out of the box. Additional type information in the JSON should not be necessary.
Edit (3) The purpose of all of this is to implement a framework that can be used by application to create typesafe objects that are stored as JSON. Normally, I would use a Java enum for this purpose, but it is possible, that clients need to read JSON documents that are created by a new version of the framework (with new values), but the client didn't update the framework version yet.
Example:
There is a class called Currency:
public class Currency extends A {
public static final Currency EUR = new Currency("EUR");
}
It is used like this:
public class Transaction {
private Currency currency;
private double amount;
}
The JSON would look like this:
{
"currency": "EUR",
"amount": 24.34
}
Now a new currency is added:
public class Currency extends A {
public static final Currency EUR = new Currency("EUR");
public static final Currency USD = new Currency("USD");
}
Clients with the new framework can produce the following JSON:
{
"currency": "USD",
"amount": 48.93,
}
One client didn't update to the new framework version. This client should be able to read the JSON without crashing.
To sum up, the ObjectMapper is provided with an instance of MyClass containing one B and one C.
Jackson will call the JsonDeserializer<A> both for B and C providing the string "value_of_b" / "value_of_c" (because by reflection, it will know that B and C are instances of A and that's the only deserializer available in the context).
Considering that in the Jackson deserializer you are in a static context (you don't have any concrete instance of A in there, you're just deserializing some string text with information that allows you to create a new instance of MyClass that looks like the serialized instance that they provided you with), then I think the only option you have is to create a factory method somewhere in your code as you guessed (I'd create it directly in the A class):
public static A getInstance(String value) {
...
}
and then inside the deserializer, simply instantiate it from that independently on whether the serialized instance was a B or a C (cause at the end of the day, you only know A so you can't handle anything else):
public final class ADeserializer extends JsonDeserializer<A> {
#Override
public A deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException {
String value = jsonParser.getText();
return A.getInstance(value);
}
}
So basically each implementation will provide you with the String value that you need to create an A, and of course you will have to create a concrete basic implementation of A on your side in order to instantiate it (because you don't know what the other implementations are, and because you need it to be concrete to create an instance).
You have to include some information during the serialization in the json. There are two ways to achieve that.
First is to enable default typing. This will add class names to your json. It will look like this:
{
"a": [
"A",
{
"value": "a"
}
],
"b": [
"B",
{
"value": "b"
}
]
}
You can enable it on ObjectMapper by calling activateDefaultTyping(ptv, DefaultTyping.OBJECT_AND_NON_CONCRETE)
Second one is to add per-class annotations. You can achieve that by adding those annotations to your abstract class.
#JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.PROPERTY,
property = "type")
#JsonSubTypes({
#Type(value = A.class, name = "a"),
#Type(value = B.class, name = "b")
})
Then the serializer will produce json like this:
{
"a": {
"type": "a",
"value": "value_of_a"
}
"b": {
"type": "b",
"value": "value_of_b"
}
}
A simple solution – that even doesn't need a lot of magic – is to use a factory method and #JsonCreator:
The base class is already known, and also the serializer:
#JsonSerialize(using = SerializerForA.class)
public class A {
protected String value;
public String getValue() {
return value;
}
}
public class SerializerForA extends JsonSerializer<A> {
#Override
public void serialize(A a, JsonGenerator gen, SerializerProvider serializers)
throws IOException {
gen.writeString(a.getValue());
}
}
The inherited classes need to implement a factory method each:
public class B extends A {
#JsonCreator
public static B create(String value) {
B b = new B();
b.value = value;
return b;
}
}
and
public class C extends A {
#JsonCreator
public static C create(String value) {
C c = new C();
c.value = value;
return c;
}
}
Now the following JSON is parsed successfully:
{
"b":"This is B",
"c":"This is C"
}
The obvious downside is, that inherited classes have to implement the factory method. I'd like to avoid that.

Rename field only on Serialization in java

I have an object named AddOnsSRO.Only on serialization I want the names of fields of the object to be changed.
Tried using #JsonProperty on getter methods but it gives me a renamed field even on usages where serialization is not involved.
public class AddOnsSRO {
private String sideCar;
private String sideCarCoverage;
#JsonSerialize
#JsonProperty("abc")
public String getSideCar() {
return sideCar;
}
public void setSideCar(String sideCar) {
this.sideCar = sideCar;
}
#JsonSerialize
#JsonProperty("xyz")
public String getSideCarCoverage() {
return sideCarCoverage;
}
public void setSideCarCoverage(String sideCarCoverage) {
this.sideCarCoverage = sideCarCoverage;
}
}
Only on serialization the following fields : sideCar and sideCarCoverage must be renamed to abc and xyz respectively.
For any other use except serialization the field names should be sideCar and sideCarCoverage only.
Please help and suggest changes or annotations accordingly.
For effecting only serializing use #JsonGetter instead of #JsonProperty
#JsonGetter("abc")
public String getSideCar() {
return sideCar;
}
Getter means that when serializing Object instance of class that has this method (possibly inherited from a super class), a call is made through the method, and return value will be serialized as value of the property.
You can add #JsonSetter to setter method for deserialize:
#JsonSetter("sideCar")
public void setSideCar(String sideCar) {
this.sideCar = sideCar;
}
your code looks good...Please upgrade your jackson lib... if you are using old

Programmatically accessing #JsonProperty from Java

I have the following POJO using Immutables+Jackson under the hood:
#JsonInclude(JsonInclude.Include.NON_NULL)
abstract class AbstractQueryRequest {
#JsonProperty("reqid")
public abstract String reqid();
#JsonProperty("rawquery")
public abstract String rawquery();
}
At some point I need to build another object based on the fields of the POJO, something along this line:
final HttpUrl.Builder urlBuilder = HttpUrl.parse(cfg.baseUrl()).newBuilder();
urlBuilder.addQueryParameter("reqid", request.reqid())
.addQueryParameter("rawquery", request.rawquery());
It's quite annoying to keep the POJO and this call aligned upon changes, I was wondering if it was possible to access programmatically each JsonProperty instead of typing the string manually.
Note that it is fine to write the getters by hand as I can easily refactor and I have the compiler double checking, but for strings I am worried for people down the line and I would like to "read" them from the POJO class somehow.
You can do it via reflection. You need to take method annotation values which annotated with JsonProperty. But I recommend you to use JsonProperty on fields, not methods.
Here is an example for your current requirement :
public class Main {
public static void main(String[] args) {
AbstractQueryRequest someType = new SomeType();
for(Method method : x.getClass().getSuperclass().getDeclaredMethods()) {
if (method.isAnnotationPresent(JsonProperty.class)) {
JsonProperty annotation = method.getAnnotation(JsonProperty.class);
System.out.println(annotation.value());
}
}
}
}
class SomeType extends AbstractQueryRequest {
#Override
public String reqid() {
return null;
}
#Override
public String rawquery() {
return null;
}
}
Output is :
rawquery
reqid

Jackson serialization: how to ignore superclass properties

I want to serialize a POJO class which is not under my control, but want to avoid serializing any of the properties which are coming from the superclass, and not from the final class. Example:
public class MyGeneratedRecord extends org.jooq.impl.UpdatableRecordImpl<...>,
example.generated.tables.interfaces.IMyGenerated {
public void setField1(...);
public Integer getField1();
public void setField2(...);
public Integer getField2();
...
}
You can guess from the example that that this class is generated by JOOQ, and inherits from a complex base class UpdatableRecordImpl which also has some bean property-like methods, which cause problems during the serialization. Also, I have several similar classes, so it would be good to avoid duplicating the same solution for all of my generated POJOs.
I have found the following possible solutions so far:
ignore the specific fields coming from superclass using mixin technique like this: How can I tell jackson to ignore a property for which I don't have control over the source code?
The problem with this is that if the base class changes (e.g., a new getAnything() method appears in it), it can break my implementation.
implement a custom serializer and handle the issue there. This seems a bit overkill to me.
as incidentally I have an interface which describes exactly the properties I want to serialize, maybe I can mixin a #JsonSerialize(as=IMyGenerated.class) annotation...? Can I use this for my purpose?
But, from pure design point of view, the best would be to be able to tell jackson that I want to serialize only the final class' properties, and ignore all the inherited ones. Is there a way to do that?
Thanks in advance.
You can register a custom Jackson annotation intropector which would ignore all the properties that come from the certain super type. Here is an example:
public class JacksonIgnoreInherited {
public static class Base {
public final String field1;
public Base(final String field1) {
this.field1 = field1;
}
}
public static class Bean extends Base {
public final String field2;
public Bean(final String field1, final String field2) {
super(field1);
this.field2 = field2;
}
}
private static class IgnoreInheritedIntrospector extends JacksonAnnotationIntrospector {
#Override
public boolean hasIgnoreMarker(final AnnotatedMember m) {
return m.getDeclaringClass() == Base.class || super.hasIgnoreMarker(m);
}
}
public static void main(String[] args) throws JsonProcessingException {
final ObjectMapper mapper = new ObjectMapper();
mapper.setAnnotationIntrospector(new IgnoreInheritedIntrospector());
final Bean bean = new Bean("a", "b");
System.out.println(mapper
.writerWithDefaultPrettyPrinter()
.writeValueAsString(bean));
}
}
Output:
{
"field2" : "b"
}
You can override the superclass' methods which you'd like to prevent from being output and annotate them with #JsonIgnore. The override shifts the control of property creation to the subclass while enabling its ability to filter it from the output.
For instance:
public class SomeClass {
public void setField1(...);
public Integer getField1();
public void setField2(...);
public Integer getField2();
#Override
#JsonIgnore
public String superClassField1(...){
return super.superClassField1();
};
#Override
#JsonIgnore
public String superClassField2(...){
return super.superClassField2();
};
...
}
You can use this as well instead of unnecessary overrides
#JsonIgnoreProperties({ "aFieldFromSuperClass"})
public class Child extends Base {
private String id;
private String name;
private String category;
}
The good use of inheritance is that the child classes extend or add functionality. So the usual way is to serialize the data.
A workarround would be to use a Value Object (VO) or Data Transfer Object (DTO) with the fields you need to serialize. Steps:
Create a VO class with the fields that should be serialized.
Use BeanUtils.copyProperties(target VO, source data) to copy the properties
Serialize the VO instance.
Add the following annotation in your Base Class :
#JsonInclude(Include.NON_NULL)

Create Object with filled variables and without any setter or constructors

I want to understand why it is possible to create and fill an object that only got private variables and an overwritten constructor.
Code example:
public class Test {
public static void main(String[] args) {
String json = "{\"id\":\"123546\"}";
Gson gson = new Gson();
Participant p = gson.fromJson(json, Participant.class);
System.out.println(p.getId());
}
}
public class Participant {
private int id;
public Participant() {
}
public int getId() {
return id;
}
}
It prints "123546" correctly.
The gson.fromJson Method has following signature: <T> T: fromJson(String json, Class<T> classOfT)
http://google-gson.googlecode.com/svn/trunk/gson/docs/javadocs/index.html
Gson, like many other JSON parser/generator libraries, uses reflection to populate fields, either directly or through methods.
Through reflection you can access public and non-public members of a class and modify them (fields) or invoke them (methods and constructors).
Your starting point should be the Class class, which provides methods to retrieve the Fields, Methods, and Constructor's of a class.
Gson uses the Class object you provide, Participant.class, to find out all the fields it needs to populate. It parses the JSON and (attempts to) maps them, one by one.

Categories