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.
Related
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)
I'm using Jackson to deserialize feeds and pull specific values from it. I'm trying to do this without know what feeds will be selected at runtime. I attempted to make a generic class that contains the method for deserializing the feed, and it works oddly enough. The problem is I can't pull any information from it without explicitly stating which class I am pulling from.
public class Motor<T> {
final ObjectMapper jsonMapper = new ObjectMapper();
public Object pullJson( String url, Object blah ) throws IOException{
#SuppressWarnings("unchecked")
T mapped = jsonMapper.readValue(new URL(url), (Class<T>) blah);
return mapped;
}
//Some other code
}
public class Root{
public static String value;
public String getValue(){
return value;
}
}
public class Propeller {
public static void main(String[] args) throws IOException{
Motor<?> motor = new Motor();
List<Class<?>> classes = new ArrayList<Class<?>>();
classes.add(Root.class)
String url = "blah.com/blah.json"
Object blah = classes.get(0);
blah = motor.pullJson(url, blah);
System.out.println(blah.value);
}
}
The only way I could think to pass a class into the method without know what the class will be was to set the class parameter as an Object and then pass a generic class onto it.
If I we're to ask for Root.value, it'll give me value's value, but I won't know what class the method will act on at runtime. And trying to act on the class declared as blah will tell me value cannot be resolved. I have a feeling my thought process here is extremely flawed in trying to pass a class as an object and reading the values that way.
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);
}
}
I wrote a TypeAdapter for a class that contains an enum attribute. This is the write method, which uses standard GSON serialization for the enum value:
#Override
public void write(JsonWriter writer, MyClass object) throws IOException {
if (object == null) {
writer.nullValue();
return;
}
writer.beginObject();
writer.name("type").value(gson.toJson(object.getType())); //this is the enum
writer.endObject();
}
When using this TypeAdapter, the produced JSON contains this part for the enum:
"type":"\"ENUM_VALUE\""
But when I use gson.toJson(object) on a class which contains this enum without a TypeAdapter, it produces:
"type":"ENUM_VALUE"
All Gson objects use the standard configuration. It produces the same result in the first version, whether I test the TypeAdapter directly or use a Gson and registering it.
Why is there a difference? I guess escaping is not needed here, so I’d like to avoid it.
Interestingly, deserialization works for both serialized versions with the TypeAdapter (with gson.fromJson(reader.nextString())).
I guess that the problem might occure because gson.toJson(object.getType()) already produces quotes: "ENUM_VALUE" and when adding them to the JsonWriter with writer.value(gson.toJson(object.getType()) it gets escaped. But how to handle this correctly, like GSON does it?
Simply your TypeAdapter is wrong. Replace it with:
public void write(JsonWriter writer, MyClass object) throws IOException {
if (object == null) {
writer.nullValue();
return;
}
writer.beginObject();
writer.name("type").value(object.getType().toString()); //this is the enum
writer.endObject();
}
In your code, you create a string from the enum doing a JSON serialization. This produces "ENUM_VALUE" (gson.toJson(object.getType())), and then it is serialized again into a string so the result is \"ENUM_VALUE\".
In my code, I get the string representation of the enum using the toString() method so no additional quotes are created.
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()...