I am using Jackson to parse object. sometime I need list of objects.
when I am using like this its working
List<MyObject> mapper.readValue(file , new TypeReference<MyObject>() {})
but when I am using it like this its not working
public class JsonMocksRepository<T>{
public T getObject() throws Exception{
return mapper.readValue(file ,new TypeReference<T>());
}
}
What I need to do ?
Basically I want to use generics to get the right class
This is because of type erasure. There is no information about the actual type represented by T available at runtime, so your TypeReference will be effectively be simply TypeReference<Object>.
If you want a generic instance of JsonMocksRepository, you will need to inject the TypeReference at construction time:
public class JsonMocksRepository<T>{
private final TypeReference<T> typeRef;
public JsonMocksRepository(TypeReference<T> typeRef) {
this.typeRef = typeRef;
}
public T getObject() throws Exception{
return mapper.readValue(file, typeRef);
}
}
Related
I have data structure that looks more or less like this
class ResponseWrapper<T> {
T response;
public ResponseWrapper(T response) {
this.response = response;
}
}
And service that handles reading that response from JSON to actual DTO.
public class GenericService<T> {
public ResponseWrapper<T> read(String json, Class<T> clazz) throws Exception {
T response = new ObjectMapper().readValue(json, clazz);
return new ResponseWrapper<>(response);
}
}
And I can call it like this:
GenericResponse<SomeData> response = new GenericService<SomeData>().read("json value", SomeData.class)
And what I'm trying to achieve is:
GenericResponse<SomeData> response = new GenericService<SomeData>().read("json value")
And I'm wondering, is it actually possible? This is obviously not working
public ResponseWrapper<T> read(String json) throws Exception {
T response = new ObjectMapper().readValue(json, T.class);
return new ResponseWrapper<>(response);
}
No. It is not possible.
Java generics work by type erasure ... and that means that the actual class associated with generic type parameter is not available at runtime. If your code needs to know that class, you need to pass a Class object explicitly.
And, yes, T.class is a compilation error.
And, yes, there is no way to get the class of T.
How i can deserialize my object from json string, if i have only string name of my class, by canonical name invoke, and json string representation of object?
In other words, i need method like this:
public <T> T deserialize(String json, String className) {
// some code
}
We have class name and json string - and we need to deserialize json to java object.
I know how to deserialize json to object if i know the class of object, for example, we can use Jackson:
String json = "{ \"color\" : \"Black\", \"type\" : \"BMW\" }";
Car car = objectMapper.readValue(json, Car.class);
But in my task there is a different classes - it may be Cat.class, Dog.class, Car.class and etc. And every class with different state. And i need universal method for deserializing.
I write something like:
public final class Utils implements Serializable {
private Utils () {
throw new AssertionError();
}
private static ObjectMapper objectMapper = new ObjectMapper();
public static String toJson(Object obj) {
String objectToJson;
try {
objectToJson = objectMapper.writeValueAsString(obj);
} catch (JsonProcessingException e) {
throw new UncheckedIOException(String.format("Can't serialize object %s to JSON string", obj), e);
}
return objectToJson;
}
public static <T> T fromJson(String json, String className) throws ClassNotFoundException, IOException {
Class<?> clz = Class.forName(className);
return (T) objectMapper.readValue(json, clz);
}
}
And it works with example of car above, but with a warning cast exception.
But maybe there is a better way?
Oh, and not all objects will be so simple. Some of them would encapsulate collections and other objects.
The problem stems from the compiler wanting to (virtually) make a different instance of this method for every type T that it is used on. However, there is nothing in the arguments to hang its hat on.
If the caller doesn't know what class to expect, other than a String representation of that class (which I'm assuming has been saved in the JSON as well?), what is he going to do with the result of this call? Since he doesn't know, he must be using it as simply Object, in which case the method might as well simply return Object.
If the caller does know the class, then he can pass in a Class as the argument, and you won't have any issues.
It occurred to me as writing this that ObectMapper must have a similar problem for some of its readValue methods, such as the one that accepts ResolvedType instead of Class. I notice that, in that class' source code, it includes /unchecked/ before the class. I"m not sure if that is to get around this issue or not, but it makes sense.
I'm writing a storage manager. I want to read from file to object. Example:
protected Object read(String key) throws Exception{
Path pathReadFrom = Paths.get(getPath(key));
if (!Files.isReadable(pathReadFrom)){
throw new FileNotFoundException();
}
Object object = JSON_MAPPER.readValue(Files.readAllBytes(pathReadFrom), Object.class);
return object;
}
JSON_MAPPER is Jackson's ObjectMapper.
public MyClass get(String id) throws Exception {
MyClass myClassObject = (MyClass) storeManager.read(id);
return myClassObject;
}
I get the next exception:
java.lang.ClassCastException: java.util.LinkedHashMap cannot be cast to com.pckg.project.dto.MyClass
How can I create universal read() method? Maybe should I set a type like second argument of read() method?
I've thought up some solution:
protected <T> T read(String key, Class<T> valueType) throws Exception{
Path pathReadFrom = Paths.get(getPath(key));
T object = JSON_MAPPER.readValue(Files.readAllBytes(pathReadFrom), valueType);
return object;
}
public MyClass get(String id) throws Exception {
MyClass object = storeManager.read(id, MyClass.class);
return object;
}
It works fine.
Yes, you do need to specify proper target type to use: otherwise Jackson has no idea of what kind of class you might want to get -- you are just telling it to "create a java.lang.Object out of JSON", which it happily does by using so-called "untyped" mode: JSON Objects become java.util.Maps, JSON Arrays java.util.Lists, JSON Strings regular Java Strings and so on.
So if you want to get JSON bound to Class MyClass, you will pass MyClass.class instead of Object.class.
How can you cast unrelated objects? In what way are LinkedHashMap and MyClass are related? You need to map them manually (or may be with help of something equivalent of AutoMapper for .NET)
Using Gson, I'm trying to de-serialize a a nested, generic class. The class structure looks like the following:
Wrapper object, simplified, but normally holds other properties such as statusMessage, which are returned along with the data-field from the server:
public class Response<T> {
private List<T> data = null;
public List<T> getData() { return this.data; }
}
Simple class, the expected output from data-field above (though as an array):
public class Language {
public String alias;
public String label;
}
Usage:
Type type = new TypeToken<Response<Language>>() {}.getType();
Response<Language> response = new Gson().fromJson(json, type);
List<Language> languages = response.getData();
Language l = languages.get(0);
System.out.println(l.alias); // Error occurs here
Where the json-variable is something like this.
However, when doing this, I recieve the following exception (on line 3, last code example):
ClassCastException: com.google.gson.internal.StringMap cannot be cast to book.Language
The exception ONLY occurs when storing the data from getData() into a variable (or when used as one).
Any help would be highly appreciated.
The problem you're actually having is not directly due to Gson, it's because of how arrays and Generics play together.
You'll find that you can't actually do new T[10] in a class like yours. see: How to create a generic array in Java?
You basically have two options:
Write a custom deserializer and construct the T[] array there as shown in the SO question I linked above
Use a List<T> instead, then it will simply work. If you really need to return an array, you can always just call List.toArray() in your method.
Edited from comments below:
This is a fully working example:
public class App
{
public static void main( String[] args )
{
String json = "{\"data\": [{\"alias\": \"be\",\"label\": \"vitryska\"},{\"alias\": \"vi\",\"label\": \"vietnamesiska\"},{\"alias\": \"hu\",\"label\": \"ungerska\"},{\"alias\": \"uk\",\"label\": \"ukrainska\"}]}";
Type type = new TypeToken<Response<Language>>(){}.getType();
Response<Language> resp = new Gson().fromJson(json, type);
Language l = resp.getData().get(0);
System.out.println(l.alias);
}
}
class Response<T> {
private List<T> data = null;
public List<T> getData() { return this.data; }
}
class Language {
public String alias;
public String label;
}
Output:
be
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.