In my spring(mvc) web application, I am using org.codehaus.jackson.map.ObjectMapper in my scala code to map my json to scala objects using case classes. My Json String is an array of json objects objects. so I am using:
val user = mapper.readValue(myJson, classOf[List[MyClass]])
This line throws an error:
Exception in thread "main"
org.codehaus.jackson.map.JsonMappingException: Can not construct
instance of scala.collection.immutable.List, problem: abstract types
can only be instantiated with additional type inform
Am I using it right or is there any other way?
The problem is the Java type erasure. classOf[List[MyClass]] at runtime is the same as classOf[List[_]]. That is why Jackson cannot know, which types of the elements to create.
Luckily Jackson does support parsing with the JavaType, which describes the types themselves.
Here a simple sample in Java:
JavaType type = mapper.getTypeFactory().constructCollectionType(List.class, MyClass.class);
mapper.readValue(myJson, type);
Because of type erasure, the parameterized type of the List is lost at runtime.
Instead, use the Scala module for Jackson and you can simply do:
mapper.readValue(myJson, new TypeReference[List[MyClass]])
So long as the Scala module has been registered - this means a Scala List will be created.
Related
I have a generic getter trait
trait Getter[A] {
def get: A
}
and I would like to parse JSON into a List of objects implementing this trait. Two such implementations:
case class CoalesceGetter[A](getters: List[Getter[String]]) extends Getter[A] {
override def get: A = getters.map(_.get).find(_ != null).orNull
}
case class AsnGetter(ipGetter: Getter[String]) extends Getter[Long] {
override def get: Long = 99L // dummy function
}
I would like to parse JSON into the correct Getter class based upon a property called function which corresponds to the class and type which corresponds to the generic type in the case of getters which need a generic (both properties are strings in the json blob I'm parsing). I've looked at custom serializers for json4s but don't see how to work with generics. Any help is appreciated!
First of all, I don't think it is a good idea to jsonify classes with type argument. I think it is a better design to define non-typed (case) classes that are direct equivalent of your json object, and use standard read/write json as provided by many libraries.
But then, to answer your question, I'd like to return another question: how would you do it "manually"?
I.e. how would you write and read different CoalesceGetter[A] with different A?
Here is a proposition: put the type arg in a json field:
"ofInt": {"type-arg":"Int", "getters":[ ... list of getters in json ...]},
"ofDouble":{"type-arg":"Double", "getters":[ ... list of getters in json ...]}
Now, if you'd write the reader, how would you instantiate the 2 ofInt and ofDouble, knowing the type-arg "Int" and "Double" (which are string!).
I see 2 solutions:
1) Either you have a hard-coded map of arg-type string => actual scala type
argType match{
case "Int" => new CoalesceGetter[Int](...)
case "Double" => new CoalesceGetter[Double](...)
}
2) Or you store and read a generalized type as string value in the arg-type string, such as the java Class.forName (see [https://stackoverflow.com/a/7495850/1206998] for example). But this is a really really bad idea IMHO.
(note: if you want to serialize any object just to reload it later or on another computer, don't use json but dedicated serialization such as the Java Serialization or kryo that is used by spark)
I am parsing JSON string from a byte-array and casting it as an object.
How do I determine the class of the object?
Object objDeserialized = gson.fromJson(jsonFromString, Object.class);
//It could be type Message or RoomDetail
gson.fromJson(jsonFromString, Object.class);
In general, this won't work because of Object.class. Gson prohibits overriding the Object class deserialization and uses ObjectTypeAdapter (see the primary Gson constructor as of Gson 2.8.0 and probably much earlier):
// built-in type adapters that cannot be overridden
factories.add(TypeAdapters.JSON_ELEMENT_FACTORY);
factories.add(ObjectTypeAdapter.FACTORY);
// the excluder must precede all adapters that handle user-defined types
factories.add(excluder);
// user's type adapters
factories.addAll(typeAdapterFactories);
If you want to use Object.class, you have to cast the result to either a primitive wrapper, null, or a List<E> or Map<K,V> -- and make some sort of analysis yourself. The rationale behind it is that you must know the result class in advance to make sure you're getting a proper deserialized object.
The best thing you can do here is making your custom parent super-type (does not really matter if it's a class or an interface), say class Message extends Base and class RoomDetail extends Base, and then registering a JsonDeserializer<Base> implementation to a GsonBuilder which can attempt to detect the real type of the Base instance. After that you can do:
gson.fromJson(jsonSource, Base.class);
See more:
Polymorphic objects deserialization:
How to parse dynamic json in android with retrofit 2 using annotations
How do I parse a nested JSON array of object with a custom Gson deserializer?
Json response parser for Array or Object
Google Gson extras, never been published as artifacts, but may be an inspiration point for you:
https://github.com/google/gson/blob/master/extras/src/main/java/com/google/gson/typeadapters/RuntimeTypeAdapterFactory.java
If you do not know the type of the JSON you want to parse you could use the JsonParser from the Gson lib to parse the JSON instead of the Gson class directly. e.g.
JsonParser parser = new JsonParser(jsonFromString);
JsonObject obj = parser.parse().getAsJsonObject();
You could then look at the properties of the JsonObject you have created to see what it is. e.g.
if (obj.has("somePropertyNameIKnownIsAMemberOfRoomDetail")) {
RoomDetail roomDetail = gson.fromJson(jsonFromString, RoomDetail.class);
} else {
Message message = gson.fromJson(jsonFromString, Message.class);
}
I have what I believe should be a simple use case.
I would like to serialize a POJO with type metadata (preferably a simple name I come up with, not the fully qualified class/package name), and later have Jackson deserialize the JSON back into the concrete class it came from by using this metadata. There is no inheritance hierarchy among classes being serialized and deserialized.
My scenario is I have a service which accepts multiple file types. For each file uploaded, the client can retrieve JSON data whose structure and type depends on the file it came from. Thus when I retrieve JSON from the service, it's not known what the concrete class is to deserialize to. I would like Jackson to figure this out based on metadata which it supplies.
For example, I'd like to be able to do this:
String json = ... // get JSON from the service
Object obj = mapper.readValue(json, Object.class) // concrete class is not known
System.out.println(obj.getClass()) // I want this to be MyConcreteClass.class
There is no inheritance hierarchy among JSON types returned.
I don't want to reveal package names or other internal service
details/structure.
I have control over Jackson's serialization process
Relevant question: Can jackson determine root object type to deserialize to when json includes type property?
Thank you so much for your help!
This can be achieved using Jackson's JavaType:
String className = "class.name.from.json.service";
JavaType dtoType = TypeFactory.defaultInstance().constructFromCanonical(className);
Object dto = new ObjectMapper().readValue(InputStream, dtoType);
assert dto.getClass().equals(dtoType.getRawClass());
I'm trying to deserialize a Json array to an ArrayList of objects. I have found some documentation on what i'm trying to do, but I'm getting an error on compile.
Here is how I'm trying to approach it with Jackson 2.2.2:
ArrayList<Friends> list = objectMapper.readValue(result, new TypeReference<ArrayList<Friends>>() {});
The error I get is:
The method readValue(String, Class<T>) in the type ObjectMapper is not applicable for the arguments (String, new TypeReference<ArrayList<Friends>>(){})
I'm guessing some of the references I have been reading is based on an older version of Jackson. How can this be accomplished in Jackson 2.2 +
Try the following:
JavaType type = objectMapper.getTypeFactory().constructCollectionType(ArrayList.class, Friends.class);
ArrayList<Friends> friendsList = objectMapper.readValue(result, type);
Note that I pulled this from the following answer: Jackson and generic type reference
As of Jackson 1.3 (a while back) they recommend you use TypeFactory.
EDIT
On further inspection, what you have above is working for me... I'm able to pass in a TypeReference sub class to readValue and everything works correctly. Are you sure you have the right type of TypeReference imported? Usually those types of errors are from accidentally importing the wrong type (some other library might have a TypeReference class).
I found some strange behavior of the Jackson JSON Processor library and i am curious whether this is intentional or a bug. Please have a look at the code below:
#JsonTypeInfo(use = Id.NAME)
public class Nut {}
…
ObjectMapper mapper = new ObjectMapper();
Nut nut = new Nut();
Object object = new Nut();
Nut[] nuts = new Nut[] { new Nut() };
Object[] objects = new Object[] { new Nut() };
System.out.println(mapper.writeValueAsString(nut));
System.out.println(mapper.writeValueAsString(object));
System.out.println(mapper.writeValueAsString(nuts));
System.out.println(mapper.writeValueAsString(objects));
Output:
{"#type":"Nut"}
{"#type":"Nut"}
[{"#type":"Nut"}]
[{}]
What i expect (and want) is the following:
{"#type":"Nut"}
{"#type":"Nut"}
[{"#type":"Nut"}]
[{"#type":"Nut"}] // <<< type information included
Do i miss something or should i file a bug report?
This is expected behavior. When traversing an object graph for serialization, Jackson uses the declared type of an object when determining what type information to include. The elements in objects have declared type Object, which you haven't told Jackson to include any type information for.
Jackson only looks at the runtime type of the top-level argument to writeValueAsString, because the method argument has type Object; it's not possible in Java to know the declared type of an object passed as an argument to a method (even with generics, thanks to type erasure), so your first two examples (writeValueAsString(nut) and writeValueAsString(object) are effectively identical).
More information here: http://jackson-users.ning.com/forum/topics/mapper-not-include-type-information-when-serializing-object-why