Jackson2 marshalling wrapper class with generics - java

I have a wrapper json object for example
{
"id": 23,
"name": "teset",
"type": "person",
"_data": {
"address": 23432
}
}
my java object would look like this
public class Wrapper<D>{
private Integer id;
private String type;
#JsonProperty("_data")
private D data;
...
}
i cannot find a way to have the object mapper do this
Wrapper<Person> wrapped = objectMapper.readValue(jsonStream,Wrapper.class);
is this not supported, i haven't been able to find much information about generics in Jackson.

There are a few problems with your code:
The main issue is that you are not specifying the desired parametrized type of Wrapper in your readValue invocation. You can fix this by using (simplified form): Wrapper<Person> wrapped = om.readValue(json, new TypeReference<Wrapper<Person>>() {});
Also, your JSON features a name property that is not apparently present in your Wrapper class. You either have it and haven't posted it, or you can configure your ObjectMapper to ignore unknown properties: objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
Here's an example:
public static class Wrapper<D> {
// making fields public for simplicity,
// use public getters and private fields of course
public Integer id;
public String type;
#JsonProperty("_data")
public D data;
}
public static class Person {
// adding address field as a public int,
// same as above, encapsulate properly in real life
public int address;
}
Then, in a main method somewhere...
ObjectMapper om = new ObjectMapper();
om.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
// your example JSON
String json = "{\"id\":23,\"name\":\"test\",\"type\":\"person\",\"_data\":"
+ "{\"address\":23432}}";
Wrapper<Person> wrapped = om.readValue(
json, new TypeReference<Wrapper<Person>>() {}
);
// printing class/hashCode of the resolved generic type
System.out.println(wrapped.data);
// casting as Person and printing actual property
System.out.println(((Person)wrapped.data).address);
Output (similar to...)
test.Main$Person#dfd3711
23432
Explanation for TypeReference, from the docs:
This generic abstract class is used for obtaining full generics type
information by sub-classing; it must be converted to ResolvedType
implementation (implemented by JavaType from "databind" bundle) to be
used. Class is based on ideas from
http://gafter.blogspot.com/2006/12/super-type-tokens.html, Additional
idea (from a suggestion made in comments of the article) is to require
bogus implementation of Comparable (any such generic interface would
do, as long as it forces a method with generic type to be
implemented). to ensure that a Type argument is indeed given.
Usage is by sub-classing: here is one way to instantiate reference to
generic type List:
TypeReference ref = new TypeReference<List<Integer>>() { };
which can be passed to methods that accept TypeReference, or resolved
using TypeFactory to obtain ResolvedType.

Related

Deserialize String as List of Strings

I have a POJO that contains the following attributes
public class Example {
#JsonProperty("inputFoo")
private String foo
#JsonProperty("inputBar")
private String bar
#JsonProperty("inputBaz")
#JsonDeserialize(using = MyDeserializer.class)
private Set<String> baz
}
The JSON that I am working with to represent this data currently represents the baz attribute as a single string:
{"inputFoo":"a", "inputBar":"b", "inputBaz":"c"}
I am using the Jackson ObjectMapper to attempt to convert the JSON to my POJO. I know that the input baz String from the JSON wont map cleanly to the Set that I am trying to represent it as, so I defined a custom Deserializer:
public class MyDeserializer extends StdDeserializer<Set<String>> {
public MyDeserializer(){}
public MyDeserializer(Class<?> vc) {
super(vc);
}
public Set<String> deserialize(JsonParser p, DeserializationContext cxt) throws IOException, JsonProcessingException {
String input = p.readValueAs(String.class);
Set<String> output = new HashSet<>();
if(input != null) {
output.add(input);
}
return output;
}
}
I am getting an IllegalArgumentException referencing the "inputBaz" attribute, which I can provide details on. Does anyone see any obvious issue with my deserializer implementation? Thanks
You do not need to implement custom deserialiser, use ACCEPT_SINGLE_VALUE_AS_ARRAY feature. It works for sets as well:
Feature that determines whether it is acceptable to coerce non-array
(in JSON) values to work with Java collection (arrays,
java.util.Collection) types. If enabled, collection deserializers will
try to handle non-array values as if they had "implicit" surrounding
JSON array. This feature is meant to be used for
compatibility/interoperability reasons, to work with packages (such as
XML-to-JSON converters) that leave out JSON array in cases where there
is just a single element in array. Feature is disabled by default.
See also:
com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize instance of java.util.ArrayList out of START_OBJECT token
Replace the 2 constructors with this no-arg constructor:
public MyDeserializer() {
super(TypeFactory.defaultInstance().constructCollectionType(Set.class, String.class));
}
ACCEPT_SINGLE_VALUE_AS_ARRAY as suggested is a good option.
Maybe your actual problem is more complicated but if not you could also try #JsonCreator instead of custom deserializer. Like:
public class Example {
#JsonCreator
public Example(#JsonProperty("inputFoo") String foo,
#JsonProperty("inputBar") String bar,
#JsonProperty("inputBaz") String strBaz) {
this.foo = foo;
this.bar = bar;
this.baz = new HashSet<>();
baz.add(strBaz);
}
private String foo;
private String bar;
private Set<String> baz;
}
Just to show that in more general case you might avoid implementing custom deserializer with #JsonCreator also but still make some simple conversions.

Use Gson library with a generic class as a field of the class to serialize

After multiple researches on Google and Stack Overflow, i haven't found a similar case to mine.
I need to use Gson library to convert a Java object to Json. The fact is that this object contains a field with a custom generic type, as follow :
SendData.java :
public class SendData {
private SendDataRequestObject<?> sendData;
// Constructor + Getters and Setters
}
Here is the class definition of SendDataRequestObject :
public class SendDataRequestObject<T> {
private String actionType;
private T parameters;
private CustomClass customClass;
//Constructor + Getters and Setters
}
And finally, the class definition of MyRequest which may be injected in SendDataRequestObject as the T parameter
public class MyRequest {
private Map<Integer, String> myMap;
private String myString1;
private String myString2;
//Constructor + Getters and Setters
}
Actually, I'm able to parse SendDataRequestObject with Gson library as follow :
SendDataRequestObject<MyRequest> requestObject = new SendDataRequestObject<MyRequest>();
//...
//Initializing and adding fields to requestObject
//...
Type token = new TypeToken<SendDataRequestObject<MyRequest>>(){}.getType();
System.out.println(new GsonBuilder().create().toJson(requestObject, token));
The output is properly set and every fields, even the generic one, are included into the final json string :
{"actionType":"verify","parameters":{"myMap":{"15789":"hreher-489hre-gdsf","13057":"rtyuiop-4g8ezg","16741":"gfd456-uiop789"},"myString1":"myStringValue1","myString2":"myStringValue2"},"customClas":{"attr1":"value1","attr2":"value2"}}
But what I need is to parse SendData class, not SendDataRequestObject class. When I try to convert this class into json string, I obtain this output :
{"sendData":{"actionType":"verify","parameters":{},"customClass":{"attr1":"value1","attr2":"value2"}}}
So, we can see that parameters field of SendDataRequestObject is not converted to Json, probably because this is a generic class.
If anybody has an idea of how to do it, I would be very grateful !
You can't do this without somehow knowing the type T at compile time in some manner due to Java's type erasure.
One option for this is the JSON can contain some information specifying the type, e.g.
{
"sendDataType": "MyRequest",
"sendData": {
...
}
}
If you then make SendData generic e.g.
SendData<T> {
private SendDataRequestObject<T> sendData;
}
you can then parse the JSON once to find out the sendDataType:
SendData<?> genericSendData = new GsonBuilder().create().toJson(requestObject, new TypeToken<SendData<?>>(){});
String sendDataType = genericSendData.sendDataType;
and use that to create a TypeToken of the right type:
switch(sendDataType) {
case "MyRequest":
return new TypeToken<MyRequest>(){};
}
And then parse the JSON again specifying the generic type now that you know it:
SendData<?> myRequestSendData = new GsonBuilder().create().toJson(requestObject, typeToken);
This works because our switch statement knows the possible types at compile time and can create TypeTokens for them.

Specifying a class that uses T

I'm using a method that takes a Class<T> as a parameter.
The class I want to pass as a parameter also uses T. It is declared as public class MyObject<T> and has a member declared as public T mMyVar; I then have 2 classes I sometimes use for mMyVar called MyVarObject1 and MyVarObject2.
Example:
private class MyObject<T> {
public T mMyVar;
}
private class MyVarObject1 {
// some variables
}
private class MyVarObject2 {
// some variables
}
Specifically, the method I'm invoking is the JacksonUtil method fromJsonArray.
I'm not sure of the proper syntax here. JacksonUtil needs to know the exact model structure so it can parse the json, but I'm having trouble figuring out the proper syntax for this line:
MyObject<MyVarObject1> result = JacksonUtil.fromJsonArray(jsonStr, MyObject<MyVarObject1>.class);
What I have there doesn't work. My IDE selects the second parameter and says, "Cannot select from parameterized type."
I had a same problem while using with retrofit, This is my solution -
public class ResponseDS<T> {
public int s;
public String e;
public T d;
}
And if you need array of object then,
public class ResponseDSs<T> {
public int s;
public String e;
public T[] d;
}
And below is how I am using it for Retrofit -
Call<ResponseDS<UserDS>> userModelCall = ZivaUtils.getRetrofit().getUser();
I think you have the same problem, hope my solution will help you :)
I do TypedToken from Gson to parse custom objects, I think you can find something similar to use with Jackson, i will edit my answer if i find something later.
You may use TypeToken to load the json string into a custom object.
Gson gson = new Gson();
//This is an example, you probably get this from your server as Json String
MyObject<MyObject1> user = new MyObject<MyObject1>();
String myObjectAsString = gson.toJson(user);
//then parse into your custom object
MyObject other = gson.fromJson(myObjectAsString, new TypeToken<MyObject<MyObject1>>(){}.getType());

How to make a JSON representation of a Java class?

Id like to represent a Class object as JSON. For example, if I have the class defintions as follows:
public class MyClass {
String myName;
int myAge;
MyOtherClass other;
}
public class MyOtherClass {
double myDouble;
}
I'd like to get the following nested JSON from a Class object of type MyClass:
{
myName: String,
myAge: int,
other: {
myDouble: double;
}
}
EDIT:
I don't want to serialize instances of these classes, I understand how to do that with GSON. I want to serialize the structure of the class itself, so that given a proprietary class Object I can generate JSON that breaks down the fields of the class recursively into standard objects like String, Double, etc.
With Jettison, you can roll your own mappings from Java to JSON. So in this case, you could get the Class object of the class you want, then map the Java returned by the getFields, getConstructors, getMethods etc. methods to JSON using Jettison.
I would recommend to use Jackson.
You can also take a look at the JSonObjectSerializer class based on Jackson which can be found at oVirt under engine/backend/manager/module/utils (you can git clone the code) and see how we used Jackson there.
Looking to do the same thing, in the end I wound up writing my own method, this does not handle all cases e.g. if one of the declared fields is a Map this will break, but this seems to be alright for most common objects:
#Override
public Map reflectModelAsMap(Class classType) {
List<Class> mappedTracker = new LinkedList<Class>();
return reflectModelAsMap(classType, mappedTracker);
}
private Map reflectModelAsMap(Class classType, List mappedTracker) {
Map<String, Object> mapModel = new LinkedHashMap<String, Object>();
mappedTracker.add(classType);
Field[] fields = classType.getDeclaredFields();
for (Field field : fields) {
if (mappedTracker.contains(field.getType()))
continue;
if (BeanUtils.isSimpleValueType(field.getType())) {
mapModel.put(field.getName(), field.getType().toString());
} else if (Collection.class.isAssignableFrom(field.getType())) {
Class actualType = (Class) ((ParameterizedType) field.getGenericType()).getActualTypeArguments()[0];
mapModel.put("Collection", reflectModelAsMap(actualType, mappedTracker));
} else {
mapModel.put(field.getName(), reflectModelAsMap(field.getType(), mappedTracker));
}
}
return mapModel;
}
The mapped tracker there because of how I handle relationships in Hibernate; without it there is an endlessly recursive relationship between parent and child e.g. child.getFather().getFirstChild().getFather().getFirstChild().getFather()...

Using Jackson ObjectMapper with Generics to POJO instead of LinkedHashMap

Using Jersey I'm defining a service like:
#Path("/studentIds")
public void writeList(JsonArray<Long> studentIds){
//iterate over studentIds and save them
}
Where JsonArray is:
public class JsonArray<T> extends ArrayList<T> {
public JsonArray(String v) throws IOException {
ObjectMapper objectMapper = new ObjectMapper(new MappingJsonFactory());
TypeReference<ArrayList<T>> typeRef = new TypeReference<ArrayList<T>>() {};
ArrayList<T> list = objectMapper.readValue(v, typeRef);
for (T x : list) {
this.add((T) x);
}
}
}
This works just fine, but when I do something more complicated:
#Path("/studentIds")
public void writeList(JsonArray<TypeIdentifier> studentIds){
//iterate over studentIds and save them by type
}
Where the Bean is a simple POJO such as
public class TypeIdentifier {
private String type;
private Long id;
//getters/setters
}
The whole thing breaks horribly. It converts everything to LinkedHashMap instead of the actual object. I can get it to work if I manually create a class like:
public class JsonArrayTypeIdentifier extends ArrayList<TypeIdentifier> {
public JsonArrayTypeIdentifier(String v) throws IOException {
ObjectMapper objectMapper = new ObjectMapper(new MappingJsonFactory());
TypeReference<ArrayList<TypeIdentifier>> typeRef = new TypeReference<ArrayList<TypeIdentifier>>(){};
ArrayList<TypeIdentifier> list = objectMapper.readValue(v, typeRef);
for(TypeIdentifier x : list){
this.add((TypeIdentifier) x);
}
}
}
But I'm trying to keep this nice and generic without adding extra classes all over. Any leads on why this is happening with the generic version only?
First of all, it works with Longs because that is sort of native type, and as such default binding for JSON integral numbers.
But as to why generic type information is not properly passed: this is most likely due to problems with the way JAX-RS API passes type to MessageBodyReaders and MessageBodyWriters -- passing java.lang.reflect.Type is not (unfortunately!) enough to pass actual generic declarations (for more info on this, read this blog entry).
One easy work-around is to create helper types like:
class MyTypeIdentifierArray extends JsonArray<TypeIdentifier> { }
and use that type -- things will "just work", since super-type generic information is always retained.

Categories

Resources