When using an ObjectMapper to transform a json String into an entity, I can make it generic as:
public <E> E getConvertedAs(String body, Class<E> type) throws IOException {
return mapper.readValue(body, type);
}
Now let's say I want to read collections. I can do:
List<SomeEntity> someEntityList = asList(mapper.readValue(body, SomeEntity[].class));
List<SomeOtherEntity> someOtherEntityList = asList(mapper.readValue(body, SomeOtherEntity[].class));
I would like to write an equivalent method of the above, but for collections. Since you can't have generic arrays in java, something like this won't work:
public <E> List<E> getConvertedListAs(String body, Class<E> type) {
return mapper.readValue(body, type[].class);
}
Here there is a solution that almost works:
mapper.readValue(jsonString, new TypeReference<List<EntryType>>() {});
The problem is that it doesn't deserialize into a list of E, but of LinkedHashMap.Entry. Is there any way of going going a step further, something like the following?
public <E> List<E> getConvertedListAs(String body, Class<E> type) {
mapper.readValue(body, new TypeReference<List<type>>() {}); // Doesn't compile
}
This method can help to read json to an object or collections:
public class JsonUtil {
private static final ObjectMapper mapper = new ObjectMapper();
public static <T>T toObject(String json, TypeReference<T> typeRef){
T t = null;
try {
t = mapper.readValue(json, typeRef);
} catch (IOException e) {
e.printStackTrace();
}
return t;
}
}
Read json to list:
List<Device> devices= JsonUtil.toObject(jsonString,
new TypeReference<List<Device>>() {});
Read json to object:
Device device= JsonUtil.toObject(jsonString,
new TypeReference<Device>() {});
public static <E> List<E> fromJson(String in_string, Class<E> in_type) throws JsonParseException, JsonMappingException, IOException{
return new ObjectMapper().readValue(in_string, new TypeReference<List<E>>() {});
}
Compiles on my computer.
Note that I haven't tested it, though.
Related
I have a class that accept a function Function<ByteBuffer, T> deserialize as a constructor argument.
I want to create a function which converts JSON into a list of objects.
Here's what I am trying to do:
public Function<ByteBuffer, List<MyObject>> deserialize(
final ObjectMapper objectMapper) {
return objectMapper.readValue(??, new TypeReference<List<MyObject>>(){});
}
Obviously the syntax is wrong here. How can I fix this?
Instead of creating such a function I would rather go with utility method because ObjectMapper.readValue() throws IOException which is checked. Therefore it should be handled because we can't propagate checked exceptions outside the Function.
If you wonder how it might look like, here it is:
public Function<ByteBuffer, List<MyObject>> deserialize(ObjectMapper objectMapper) {
return buffer -> {
try {
return objectMapper.readValue(buffer.array(),new TypeReference<List<MyObject>>() {
});
} catch (IOException e) {
e.printStackTrace();
return null;
}
};
}
Instead, let's consider a utility-class:
public static final ObjectMapper objectMapper = // initializing the mapper
public static List<MyObject> deserialize(ByteBuffer buffer) throws IOException {
return objectMapper.readValue(buffer.array(),new TypeReference<>() {});
}
I wrote a Generic method which works fine converting any Json object to Generic Object.
public <T> T convertJsontoObject (String jsonObj, Class<T> any Type)
throws JsonMappingException, JsonProcessingException
{
ObjectMapper objectMap = new ObjectMapper();
return objectMap.readValue(jsonObj, any Type);
}
But has an issue while converting the Generic Object type to JSON format with the below code format. Would someone help me or guide me with related code.. I am not sure how to retain an Object from Generic class type as the method doesn't support for generic and I want to have a Generic method to perform the conversions....
public <T> String convertObjectToJson(Class<T> anyType)
{ String jsonStringObj = "";
try { jsonStringObj = new
ObjectMapper().writerWithDefaultPrettyPrinter().writeValueAsString(anyType);}
}
When you return a String you don't need to use generics and you can receive only an Object on methods
// you can treate try and catch inside them as you prefer
public static <T> T fromJson(String json, Class<T> classToReturn) throws Exception{
return new ObjectMapper().readValue(json, classToReturn);
}
public static String toJson(Object obj) throws Exception{
return new ObjectMapper().writeValueAsString(obj);
}
public static String toJsonPrettyNonNullTreatingDateTypes(Object obj) throws Exception{
return new ObjectMapper()
.setSerializationInclusion(Include.NON_NULL)
.setSerializationInclusion(Include.NON_EMPTY)
.registerModule(new JavaTimeModule() );
.writerWithDefaultPrettyPrinter()
.writeValueAsString(obj);
}
using them
MyObject obj = fromJson(jsonString, MyObject.class );
String json = toJson(new MyObject()); // Object can accept any types
String json = toJsonPrettyNonNullTreatingDateTypes(new MyObject());
I have following code:
public static <T> T jsonToObject(String json, Class<T> object) {
ObjectMapper mapper = new ObjectMapper();
try {
return mapper.readerFor(object).readValue(json);
} catch (JsonProcessingException e) {
e.printStackTrace();
}
return null;
}
What can I do to not return null?
Caller will have to deal with this being null/missing anyway so may as well specify that your method throws a JsonProcessingException and do exception handling in the caller. Then the caller can do whatever it needs for the type it needs.
Otherwise you're just handling the same issue twice.
public static <T> T jsonToObject(String json, Class<T> object) throws JsonProcessingException {
ObjectMapper mapper = new ObjectMapper();
return mapper.readerFor(object).readValue(json);
}
Considering that:
ObjectReader readerFor(Class<?> type) - Factory method for
constructing ObjectReader that will read or update instances of
specified type
<T> T readValue(String content, TypeReference valueTypeRef) - Method
to deserialize JSON content from given JSON content String.
So the method is returning an instance of the generic type Class<T>, so you can use:
public static <T> T jsonToObject(String json, Class<T> object) {
ObjectMapper mapper = new ObjectMapper();
T instance=null;
try {
instance = mapper.readerFor(object).readValue(json);
} catch (JsonProcessingException e) {
e.printStackTrace();
}
if (instance == null) {
instance = ((Class) ((ParameterizedType) this.getClass().
getGenericSuperclass()).getActualTypeArguments()[0]).newInstance();
}
return instance;
}
Now you will get a new instance if the result is null.
I'm trying to generalise this method:
public EventStream<Greeting> deserialize(String value){
ObjectMapper mapper = new ObjectMapper();
EventStream<Greeting> data = null;
try {
data = new ObjectMapper().readValue(value, new TypeReference<EventStream<Greeting>>() {});
} catch (IOException e) {
e.printStackTrace();
}
return data;
}
where EventStream is:
public class EventStream<T> {
private EventHeaders headers;
#JsonDeserialize
private T payload;
}
What I'd like to have is replace the specific Object Greeting with a generic, in the deserialize method.
I tried with this:
public <T> EventStream<T> deserialize(String value){
ObjectMapper mapper = new ObjectMapper();
EventStream<T> data = null;
try {
data = new ObjectMapper().readValue(value, new TypeReference<EventStream<T>>() {});
} catch (IOException e) {
e.printStackTrace();
}
return data;
}
But the payload inside the EventStream result is deserialized as LinkedHashMap. It seems like TypeReference ignored the generic type.
Any idea?
Thanks
What you encountered here is a common problem caused by something called type erasure, the way java implements generics.
Type erasure can be explained as the process of enforcing type
constraints only at compile time and discarding the element type
information at runtime. [1]
So at the time you try to deserialize your object, the type T is not known and it is just treated as Object and the deserialization result will default default to Map (LinkedHashMap to be precise).
You could make your method generic by passing your targetClass as an additional argument to the function call like so:
public <T> EventStream<T> deserialize(String value, Class<T> targetClass)
Then you use the TypeFactory of your mapper to create a type of this targetClass
JavaType type = mapper.getTypeFactory().constructParametricType(EventStream.class, targetClass);
which you can pass to the readValue method:
data = mapper.readValue(value, type);
Complete code:
public <T> EventStream<T> deserialize(String value, Class<T> targetClass){
ObjectMapper mapper = new ObjectMapper();
JavaType type = mapper.getTypeFactory()
.constructParametricType(EventStream.class, targetClass);
try {
return mapper.readValue(value, type);
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
[1] https://www.baeldung.com/java-type-erasure
public static <T> List<T> convertJSONStringTOListOfT(String jsonString, Class<T> t){
if(jsonString == null){
return null;
}
ObjectMapper mapper = new ObjectMapper();
try
{
List<T> list = mapper.readValue(jsonString, new TypeReference<List<T>>() {});
return list;
} catch (JsonGenerationException e) {
e.printStackTrace();
} catch (JsonMappingException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
I have the above method, when I try to invoke it using :
list = convertJSONStringTOListOfT(str, CustomAssessmentQuestionSetItem.class);
The returned list is List<LinkedHashMap> not List<CustomAssessmentQuestionSetItem>
Although if I don't use generics then the below code works fine :
list = mapper.readValue(str, new TypeReference<List<CustomAssessmentQuestionSetItem>>() {});
Both invocations appear the same to me. Unable to understand why the generic one is creating a List<LinkedHashMap> instead of List<CustomAssessmentQuestionSetItem>
FYI : I've also tried changing the method signature to
public static <T> List<T> convertJSONStringTOListOfT(String jsonString, T t)
and the corresponding invocation to
list = convertJSONStringTOListOfT(str,new CustomAssessmentQuestionSetItem());
but it didn't worked.
Since you have the element class you probably want to use your mapper's TypeFactory like this:
final TypeFactory factory = mapper.getTypeFactory();
final JavaType listOfT = factory.constructCollectionType(List.class, t);
Then use listOfT as your second argument to .readValue().