i've been wondering how to correctly solve this problem
I have a data model like this:
Class B
String fieldB1;
String fieldB2;
Class A
String fieldA1;
String fieldA2;
List<B> fieldA3;
(and then another third class which has the same hierarchy as the other with fields and a list of A objects, but for simplicity let's stick with A and B)
Now on the other side i have to deserialize these classes in classes with the same name and params just with different data types
So ^ must read as:
Class B
int fieldB1;
double fieldB2;
Class A
float fieldA1;
float fieldA2;
List<B> fieldA3;
Since i'm not experienced, my first guess was to write customs Deserializer in jackson for A and B, and when I deserialize a Class like B which does not have reference to other classes with custom deserialization methods, the conversion is easy.
But what about creating a custom deserializer for Class A? When I have to deserialize the fieldA3, aka the list of B objects, how should I operate? Should try to call in some way in the ClassACustomDeserializer the ClassBCustomDeserializer? How to do that?
Or is there another simpler solution to just tell jackson to transform some String fields in some other types based on my personal mapping?
This is how i would deserialize B
public class BDeserializer extends StdDeserializer<B> {
public BDeserializer() {
this(null);
}
public BDeserializer(Class<?> vc) {
super(vc);
}
#Override
public B deserialize(JsonParser jp, DeserializationContext ctxt)
throws IOException, JsonProcessingException {
JsonNode node = jp.getCodec().readTree(jp);
int fieldB1= node.findValue("fieldB1").asInt();
double fieldB2= node.findValue("fieldB2").asDouble();
return new B(fieldB1,fieldB2);
}
}
Jackson is smart enough to convert the text value to an appropriate numeric type, so it should be able to deserialize a JSON like:
{ "fieldB1": 10, "fieldB2" : "0.333" }
to your
Class B
int fieldB1;
double fieldB2;
just nice, even w/o using a custom deserializer.
If you want to stick with a custom deserializer, for whatever reason,
you can either use JsonNode.traverse() to create a sub-parser:
JsonParser parser = node.findValue("fieldA3").traverse();
parser.setCodec(jp.getCodec());
List<B> list = parser.readValueAs(new TypeReference<List<B>>() {});
or navigate the token stream yourself, instead of using find:
while(jp.nextToken() != JsonToken.END_OBJECT) {
if(jp.currentToken() == JsonToken.FIELD_NAME) {
switch (jp.getCurrentName()) {
//...
case "fieldA3":
jp.nextToken();
list=jp.readValueAs(new TypeReference<List<ClassB>>() {}));
break;
}
}
}
the latter should be more efficient, if performance is of concern.
Related
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.
In my web application that is using Spring, we want use a custom JSON structure. Spring by default takes a POJO like this:
public class Model {
private int id;
private String name;
public Model(){}
public Model(int id, String name){
this.id = id;
this.name = name;
}
}
and turns it into this:
{"id":1, "name":"Bob"}
With our application, we want to turn it into this instead:
[1, "Bob"]
I want to use Spring's default serialization logic that detects the Java type (int, String, Collection, etc.) and maps to the appropriate JSON type, but just change the wrapping object to an array rather than and object with fields.
This is the Serializer I have so far (which will be implemented in the model with #JsonSerialize(using = Serializer.class)), but would prefer not to rewrite all the logic Spring already has implemented.
public class Serializer extends JsonSerializer<Model> {
#Override
public void serialize(Model value, JsonGenerator jgen, SerializerProvider provider)
throws IOException, JsonProcessingException {
jgen.writeStartArray();
jgen.writeString(value.id);
.... other values ...
jgen.writeEndArray();
}
}
How can I hook into the pre-existing Serializer so that this new serializer will work with any POJO as the default one does (not just the Model class, but any similar or child class we need to serialize to an array)? This could have mixed properties and no specific naming convention for the properties.
I want to avoid writing a custom serializer for every different Model class (the ... other values ...) section.
Take a look at Apache BeanUtils library, in particular, pay attention to the BeanUtils.populate() method.
What that method does is to convert any given Object to a Map<String, Object>, based on JavaBeans conventions. In the keys you'd have the attribute names, while in the values you'd have every attribute's value. That method should be enough for standard cases. Read the documentation carefully, to check how to handle special cases.
Model model = ...; // get your model from some place
Map<String, Object> properties = new HashMap<>();
BeanUtils.populate(model, properties);
// Exception handling and special cases left as an excercise
The above recursively fills the properties map, meaning that if your Model has an attribute named otherModel whose type is OtherModel, then the properties map will have another map at the entry that matches the otherModel key, and so on for other nested POJOs.
Once you have the properties map, what you want to serialize as the elements of your array will be in its values. So, something like this should do the job:
public List<Object> toArray(Map<String, Object> properties) {
List<Object> result = new ArrayList<>();
for (Object obj : properties.values()) {
Object elem = null;
if (obj != null) {
Class<?> clz = obj.getClass();
if (Map.class.isAssignableFrom(clz)) {
elem = toArray((Map<String, Object>) obj); // recursion!
} else {
elem = obj;
}
}
result.add(elem); // this adds null values
// move 1 line up if you don't
// want to serialize nulls
}
return result;
}
Then, after invoking the toArray() method, you'd have a List<Object> ready to serialize using the standard Spring mechanisms. I even believe you won't need a specific serializer:
List<Object> array = toArray(properties);
return array; // return this array, i.e. from a Controller
Disclaimer:
Please use this as a guide and not as a final solution. I tried to be as careful as possible, but the code might have errors. I'm pretty sure it needs special handling for arrays and Iterables of POJOs. It's undoubtedly lacking exception handling. It works only for POJOs. It might explode if the supplied object has circular references. It's not tested!
You could use #JsonValue annotation for this.
Example:
public class Model {
private int id;
public Model(){}
public Model(int id){
this.id = id;
}
#JsonValue
public int[] getValue() {
return new int[]{this.id};
}
}
I have enabled Polymorphic serialization support by adding annotations on the base class. I am able to seriazlize an individual object successfully and it is writing the type information as part of serialized data. However, the same is not happening if I store the objects in a list and serialize it.
It seems this issue was fixed in 1.6.3 (http://jira.codehaus.org/browse/JACKSON-362)
I am using Jackson 2.3.2 and still facing the issue.
Does somebody know how to fix this?
Code:
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY,property = "type")
#JsonSubTypes({#Type(value = Derived.class, name = "derived")})
public abstract class Base {
}
public class Derived extends Base {
private String field;
public String getField() {
return field;
}
public void setField(String field) {
this.field = field;
}
}
public class Test {
public static void main(String[] args) throws JsonProcessingException {
ObjectMapper mapper = new ObjectMapper();
Derived d = new Derived();
d.setField("Name");
Base b = d;
System.out.println(mapper.writeValueAsString(b));
List<Base> list = new ArrayList<Base>();
list.add(d);
System.out.println(mapper.writeValueAsString(list));
}
}
Output:
{"type":"derived","field":"Name"}
[{"field":"Name"}]
Thanks,
Praveen
The answer is at https://github.com/FasterXML/jackson-databind/issues/699
This is due to Java type erasure: when serializing a List, all Jackson see as a type is List (roughly equivalent to List). And since type Object does not have polymorphic type information (annotation), none will be written.
So this is not a bug in Jackson, but an unfortunate feature of Java Type Erasure.
It does not apply to arrays, since they retain element type information (arrays are not generic; arrays of different types are different classes, whereas generic typing is mostly compile-time syntactic sugar).
There are three main ways to deal with this:
pass full generic type using TypeReference (ObjectMapper has method like mapper.writerFor(new TypeReference<List<Base>>() { }).writeValue(....)
Sub-class List to something like public class BaseList extends ArrayList<Base>() { }, and pass that: this type WILL retain type information
Avoid using root-level List and Maps
I personally recommend doing (3), since this avoids all related problems with type erasure.
In my opinion JSON root value should always be a JSON Object, usually serialized to/from POJO.
Approach (2) will however work, and this is what most users do. It does require use of an additional helper class.
Approach (1) may or may not work; problem being that forcing type information does also affect actual value serialization. So while it will add type id, it may result in some properties not being serialized.
This problem can be solved by using arrays, instead of list (since list does type erasure):
for example, your above test-case could be written as:
public class Test {
public static void main(String[] args) throws JsonProcessingException {
ObjectMapper mapper = new ObjectMapper();
Derived d = new Derived();
d.setField("Name");
Base b = d;
System.out.println(mapper.writeValueAsString(b));
List<Base> list = new ArrayList<Base>();
list.add(d);
System.out.println(mapper.writeValueAsString(
list.toArray(new Base[list.size]) // <--This Part
));
}
}
I had the same issue with object array. Object[] doesn't carry type information but individual objects do. It's a shame that jackson doesn't handle that automatically.
Two possible solutions:
1. Typed array serialization works just fine:
Base[] myArray = Base[]{d};
mapper.writeValueAsString(myArray)
this will actually produce expected result as Base[] has type information.
I solved that my issue with custom serializer.
Serializer:
public class ObjectArraySerializer extends StdSerializer<Object[]> {
public ObjectArraySerializer(final Class<Object[]> vc) {
super(vc);
}
#Override
public void serialize(
final Object[] data,
final JsonGenerator gen,
final SerializerProvider provider) throws IOException {
gen.writeStartArray();
for (Object obj : data) {
gen.writeObject(obj);
}
gen.writeEndArray();
}
}
ObjectMapper configuration:
ObjectMapper objectMapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
module.addSerializer(
Object[].class,
new ObjectArraySerializer(Object[].class));
objectMapper.registerModule(module);
i have the following problem.
I have to parse a json request into an object that contains a generic type field.
EDIT
i have made some tests using a regular class type (so i make it work before i replace it with generic). Now parsing for a single element works great.
The issue is when i need to parse out a list object out of that class.
So i have to inform jackson somehow that my T is of type list instead of just AlbumModel.
Here is what i have tried.
#Override
public ListResponseModel<AlbumModel> parse(String responseBody) throws Exception {
JavaType type = mapper.getTypeFactory().constructParametricType(ResponseModel.class,
AlbumModel.class);
return mapper.readValue(responseBody,
mapper.getTypeFactory().constructParametricType(ResponseModel.class, type));
}
But the code above doesn't work. what is the solution for something like this?
my generic type in the ListResponseModel is defined like: List<T> data
succeeded like:
public class BaseResponseModel<T> {
#JsonProperty("data")
private T data;
#JsonProperty("paginations")
private PaginationModel pagination;
}
so far i have the following code but it always parses into a Hash.
public class ResponseParser extends BaseJacksonMapperResponseParser<ResponseModel<AlbumModel>> {
public static final String TAG = ResponseParser.class.getSimpleName();
#Override
public ResponseModel<AlbumModel> parse(String responseBody) throws Exception {
return mapper.readValue(responseBody,
mapper.getTypeFactory().constructParametricType(ResponseModel.class, AlbumModel.class));
}
}
public abstract class BaseJacksonMapperResponseParser<T> implements HttpResponseParser<T> {
public static final String TAG = BaseJacksonMapperResponseParser.class.getSimpleName();
public static ObjectMapper mapper = new ObjectMapper();
static {
mapper.disable(Feature.FAIL_ON_UNKNOWN_PROPERTIES);
mapper.enable(Feature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT);
mapper.configure(SerializationConfig.Feature.WRAP_ROOT_VALUE, true);
}
}
I agree with eugen's answer but just wanted to expand on it a bit. The first step is to refactor your parse method so it takes a second argument. Instead of allocating the type reference in your method, you require the caller to pass in a TypeReference instance.
public BaseResponseModel<T> parse(String responseBody, TypeReference<T> ref) throws Exception {
return mapper.readValue(responseBody, ref);
}
Unfortunately your snippet does not show the code which calls parse - so I'll make something up:
BaseResponseParser<Collection<Person>> parser = new BaseResponseParser<Collection<Person>>();
BaseResponseModel<Collection<Person>> result = parser.parse(jsonText, new TypeReference<Collection<Person>>(){});
Notice that when the TypeReference instance is compiled in this case, it a type reference to the real concrete class that we expect.
You could do the same thing passing in a Class at runtime, however TypeReference is a bit more powerful because it even works when type T is a generic collection. There is some magic in the TypeReference implementation that allows it to hold onto type information that would normally be erased.
[update]
Updated to use Collection<Person>. Note - as far as I know as List<Whatever> should work also, but I double checked a project where I was using jackson to deserialize collections. Base class Collection definitely worked so I stayed with that.
Your type T will be "erased" at runtime, so Jackson does not know what is the real type of T and deserializes it to a Map. You need a second parameter to your parse method that will be Class<T> clazz or TypeReference<T> or java.lang.reflect.Type.
EDIT
Small explanation on the magic of TypeReference. When you do new XX() {} you are creating a anonymous class, so if it is a class with typevariables (parameterized if you prefer), new X<List<Y>>() {}, you will be able to retrieve List<Y> as a java Type at runtime. It is very similar as if you had done :
abstract class MyGenericClass<T> {}
class MySpecializedClass extends MyGenericClass<List<Y>> {}
Since you're using Jackson you probably need to create a custom JsonDeserializer or JsonSerializer depending on whether you're handing the response or request. I've done this with Dates because on my response I want a standard view. I'm not 100% positive it will work with a generic field though. Here is an example of what I'm doing:
public class DateSerializer extends JsonSerializer<Date> {
private SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZZ");
#Override
public void serialize(Date value, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonProcessingException {
String dateString = dateFormat.format(value);
jgen.writeString(dateString);
}
}
Then I just add it to my class like so:
#JsonSerialize(using = DateSerializer.class)
public Date getModifiedDate() {
return modifiedDate;
}
I am serializing and deserializing following domain object to JSON using Jackson 1.8.3
public class Node {
private String key;
private Object value;
private List<Node> children = new ArrayList<Node>();
/* getters and setters omitted for brevity */
}
Object is then serialized and deserialized using following code
ObjectMapper mapper = new ObjectMapper();
mapper.writeValue(destination, rootNode);
And then later deserialized with
mapper.readValue(destination, Node.class);
The original values of the object are either Strings, Doubles, Longs or Booleans. However, during serialization and deserialization Jackson transforms Long values (such as 4) to Integers.
How can I "force" Jackson to deserialize numeric non-decimal values to Long instead of Integer?
There is a new feature in Jackson 2.6 specifically for this case:
configure the ObjectMapper to use DeserializationFeature.USE_LONG_FOR_INTS
see https://github.com/FasterXML/jackson-databind/issues/504
cowtowncoder pushed a commit that closed this issue on May 19, 2015
Fix #504 and #797
If type is declared as java.lang.Object, Jackson uses 'natural' mapping which uses Integer if value fits in 32 bits. Aside from custom handlers you would have to force inclusion of type information (either by adding #JsonTypeInfo next to field / getter; or by enabling so-called "default typing").
I ended up creating a custom deserializer, since in my application logic there are only four different types for values (Double, Long, Integer and String).
I'm not sure if this is the best possible solution but it works for now.
public class MyDeserializer extends JsonDeserializer<Object> {
#Override
public Object deserialize(JsonParser p, DeserializationContext ctxt)
throws IOException, JsonProcessingException {
try {
Long l = Long.valueOf(p.getText());
return l;
} catch (NumberFormatException nfe) {
// Not a Long
}
try {
Double d = Double.valueOf(p.getText());
return d;
} catch (NumberFormatException nfe) {
// Not a Double
}
if ("TRUE".equalsIgnoreCase(p.getText())
|| "FALSE".equalsIgnoreCase(p.getText())) {
// Looks like a boolean
return Boolean.valueOf(p.getText());
}
return String.valueOf(p.getText());
}
}
I've used something like the below to work around this problem.
#JsonIgnoreProperties(ignoreUnknown = true)
public class Message {
public Long ID;
#JsonCreator
private Message(Map<String,Object> properties) {
try {
this.ID = (Long) properties.get("id");
} catch (ClassCastException e) {
this.ID = ((Integer) properties.get("id")).longValue();
}
}
}
In my case I did not want to use DeserializationFeature.USE_LONG_FOR_INTS for ObjectMapper, because it would affect all the project. I used the next solution: use a custom deserializer:
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.ObjectCodec;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonNode;
import java.io.IOException;
public class LongInsteadOfIntegerDeserializer extends JsonDeserializer<Object> {
#Override
public Object deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException {
ObjectCodec codec = jsonParser.getCodec();
JsonNode jsonNode = codec.readTree(jsonParser);
if (jsonNode.isInt()) {
return jsonNode.asLong();
}
return codec.treeToValue(jsonNode, Object.class);
}
}
And add it to the field of type Object:
public class SomeTOWithObjectField {
//... other fields
#JsonDeserialize(using = LongInsteadOfIntegerDeserializer.class)
private Object value;
//... other fields
}
And it deserialized integers as longs, but other types like String, boolean, double etc. were deserialized as they should be by default.
If you want to wrap a primitive into specific class, you can do follow (example in Kotlin):
data class Age(
#JsonValue
val value: Int
)
And now, your Int primitives will be parsed into Age class and vice versa - Age class into Int primitive.
In jackson 2 we can use TypeReference to specify the generic type in detail. There is and overloaded method for readValue() which takes the TypeReference as the 2nd parameter:
readValue([File|String|etc], com.fasterxml.jackson.core.type.TypeReference))
If you want to get a list of Long instead of Integer, you can do the following.
ObjectMapper mapper = new ObjectMapper();
TypeReference ref = new TypeReference<List<Integer>>() { };
List<Integer> list = mapper.readValue(<jsonString>, ref);
This works for maps as well:
TypeReference ref = new TypeReference<Map<String,Long>>() { };
Map<String, Long> map = mapper.readValue(<jsonString>, ref);
In your case, you can convert your class to a generic one. i.e Node<T>. When creating nodes, do as Node<String/Integer/etc> And use the type reference to read the value.