I am trying to deserialise a JSON-RPC object with Jackson. The format of JSON-RPC is :
{ "result": "something", "error": null, "id": 1}
In my case the result property is an generic Object.
I have a class for deserilising the response:
public class JsonRpcResponse {
private Object result;
private JsonRpcError error;
private int id;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public JsonRpcError getError() {
return error;
}
public void setError(JsonRpcError error) {
this.error = error;
}
public Object getResult() {
return result;
}
public void setResult(Object result) {
this.result = result;
}
}
I can get the response object with:
JsonRpcResponse jsonResp = mapper.readValue(response, JsonRpcResponse.class);
I want to have a generic method that deserializes this result object by passing to the method the type of the object (or the class if you want) that is going to be deserialized to. This way I can pass any type of object depending of the response I expect.
For example, I have this class with two properties:
public class JsonEventProperties {
private String conditon;
private String usage;
public JsonEventProperties(String condition, String usage) {
this.conditon = condition;
this.usage = usage;
}
public JsonEventProperties() {
throw new UnsupportedOperationException("Not yet implemented");
}
public String getConditon() {
return conditon;
}
public void setConditon(String conditon) {
this.conditon = conditon;
}
public String getUsage() {
return usage;
}
public void setUsage(String usage) {
this.usage = usage;
}
}
The result object inside the response for the above case will be:
"result": {"condition":"test","usage":"optional"}
I tried:
mapper.readValue(result,objectClass)
where result is a JsonNode intance of the result (Which for some reason is a LinkedHashMap) and objectClass the class I want it to deserialize to. But this is not working.
I busted my head all day with different ways of doing this but I probably do not understand who Jackson works.
Can anyone help me with this?
Thank you in advance.
I understand the original question to be asking about polymorphic deserialization of the "result" object.
Jackson now has a built-in mechanism available for this, using the #JsonTypeInfo and #JsonSubTypes annotations. (ObjectMapper has methods available as alternatives to using the annotations.) Further information is available in the official docs at http://wiki.fasterxml.com/JacksonPolymorphicDeserialization. Also, I posted a few use examples of this at http://programmerbruce.blogspot.com/2011/05/deserialize-json-with-jackson-into.html.
However, if you're stuck deserializing JSON that, in the target object, does not have an element that identifies the type by some name, then you're stuck with custom deserialization, where you'll have to determine based on some object content what the type should be. One of the last examples in the same blog posted I linked above demonstrates this approach, using the existence of particular JSON elements in the target object to determine the type.
Check out jsonrpc4j on github:
https://github.com/briandilley/jsonrpc4j
I had the same issue, this was my solution.
I added one more field to the object, so when building the object, i am setting the field value with class name, when deserializing it i am using
mapper.convertvalue(object, Class.forName(field value)
In your case
private Object result;
In the result object add one more field "className", while serializing the class set the value "className" with the name of the class you are treating as result object.
while deserializing the object
JsonRpcResponse jsonResp = mapper.readValue(response, JsonRpcResponse.class);
in jsonResp you will have Object result, String className, here the object is of type linkedhashmap
Now to convert to your object
objectmapper.convertValue(result, Class.forName(className))
The above code will get you the generic object which you want .
Hope this helps
Related
I'm using Gson to cast my class to Json, I have a field called payload
which must to be my class itself.
My class is as follow:
public class MyClass {
private Long id;
private String name;
private Object payload;
}
But when I use it as follow:
MyClass myClassObj = MyClass()
myClassObj.setId(1L);
myClassObj.setName("Example");
myClassObj.setPayload(myClassObj);
And I see the result of:
String result = new Gson().toJson(myClassObj);
The result does not contain payload data object.
{"id":1, "name":"Example"}
I need something like:
{"id":1, "name":"Example", "payload": {"id":1, "name":"Example"}}
Thanks in advance.
Any help will be useful.
You will need to change it to be defined as such
public class MyClass {
private Long id;
private String name;
private MyClass payload = null;
}
Gson serialized parameters that are not transient. Object does not have the "id" and "name" parameters you are wanting to serialize. MyClass does. Ensure that payload's default value is null otherwise you may have an infinite loop on your hands when serializing to json.
I found a solution, Object can be anything, so before to set payload of my class I made the follow:
myClassObj.setPayload(new Gson.fromJson(myClassObj.toString(), myClassObj.class);
Note:
In MyClass I have override toString method as follow:
#Override
public String toString() {
return new Gson().toJson(this);
}
If someone has a better solution, feel free to post it.
I have an program that already transform POJOs to xml via JAXB and this is done by adding a the elements to a list in this form
List<JAXBElement<?>> elementsToTransform = new ArrayList<JAXBElement<?>>();
In other method I just transform and populate the elementsToTransform as xml, the firm of this method is something like this
private List<JAXBElement<?>> transform(Student student)
Now I need to add a way to transform to Json via Gson and I want to abstract both declaration and method in order to support both formats but the implementation is pretty hardcoded to xml and I need to fit this solution as the way it is build with xml because this depends on other functionality that I do not want to modify so I would know if there is a way I can support both formats and which will be the better choice for this problem
At the end I would like to have something like this in the elementsToTransform list
JAXBElement(0)
JAXBElement(1)
JAXBElement(2)
String(3)(this will be Json)
Implement a wrapped class to incorporate both a String or JAXBElement. This can be identified by the enum Type. The convertion for each type can be implemented as an interface method.
interface IConvert{
JSONObject convert(WrappedObject o){
}
The Type Enum can be defined as follows
enum Type implements IConvert{
STRING {
public JSONObject convert(WrappedObject o){
String str = o.getString();
//use Gson here and convert;
return json;
}
},
JAXB {
public JSONObject convert(WrappedObject o){
JAXBElement jax = o.getJAX();
//use Gson here and convert;
return json;
}
};
}
Finally the WrappedObject will look like this :
class WrappedObject {
private String str;
private JAXBElement jax;
private Type type;
public WrappedObject(String str){
this.str=str
this.type=Type.STRING;
}
public WrappedObject(JAXBElement jax){
this.jax=jax
this.type=TYPE.JAXB;
}
public TYPE getType(){
return this.type;
}
public JSONObject convert(){
return type.convert(this);
}
//..other getters and setters
}
Hope this answers your design pattern question.
When you have a class to be converted to a json, if it contains a BigDecimal attribute, it will return a json like this:
Response {
BigDecimal price;
}
//json:
{
price: 20.20
}
Note that BigDecimal is a class. Its behavior is like an primitive (integer, float).
I want to produce the same behavior (a class return a single information to json)
Example:
class Response {
Money value
}
Money {
BigDecimal price;
}
//What is returning:
{
value : { price: 20.20 }
}
//What I want:
{
value : 20.20
}
Gson doesn't have such a feature out of the box. You'll need to implement it yourself.
If it's just for the Response type, you can simply implement your own TypeAdapter.
class ResponseTypeAdapter extends TypeAdapter<Response> {
#Override
public void write(JsonWriter out, Response value) throws IOException {
out.beginObject();
out.name("value");
// check for null, if applicable, and use a default value, or don't write anything at all
out.value(value.getValue().getPrice());
out.endObject();
}
#Override
public Response read(JsonReader in) throws IOException {
// implement the deserialization
}
}
Then register it.
Gson gson = new GsonBuilder().registerTypeAdapter(Response.class, new ResponseTypeAdapter()).create();
// test it
String json = gson.toJson(new Response(new Money(new BigDecimal("20.20"))));
This would now serialize to
{"value":20.20}
If you can use Jackson, it comes with a #JsonValue annotation which does this for you. For example,
class Money {
private final BigDecimal price;
public Money(BigDecimal bigDecimal) {
this.price = bigDecimal;
}
#JsonValue // <<< this
public BigDecimal getPrice() {
return price;
}
}
used with
ObjectMapper mapper = new ObjectMapper();
String json = mapper.writeValueAsString(new Response(new Money(new BigDecimal("20.20"))));
will generate
{"value":20.20}
The javadoc states
Marker annotation similar to javax.xml.bind.annotation.XmlValue that
indicates that results of the annotated "getter" method (which means
signature must be that of getters; non-void return type, no args) is
to be used as the single value to serialize for the instance. Usually
value will be of a simple scalar type (String or Number), but it can
be any serializable type (Collection, Map or Bean).
At most one method of a Class can be annotated with this annotation;
if more than one is found, an exception may be thrown. Also, if method
signature is not compatible with Getters, an exception may be thrown
(whether exception is thrown or not is an implementation detail (due
to filtering during introspection, some annotations may be skipped)
and applications should not rely on specific behavior).
Ok, last time I asked this question, it was downvoted by 3 people before I deleted it. May be it wasn't clear, sorry my fault. So I am using retrofit to make api hits. The api returns a JSON which has a field data which can be null. SO the JSON can be something like this.
{
"title": "Product",
"description": "A product from Acme's catalog",
"type": "object"
"data":null
}
When it isn't null it will be something like
{
"title": "Product",
"description": "A product from Acme's catalog",
"type": "object"
"data":{"b":"xyz","a":123}
}
Now I have a model class for this object like this
public class A {
#Expose
public String title;
#Expose
public String description;
#Expose
public String type;
#Expose
public Data data =new Data();
public Data getData() {
return data;
}
public void setData(Data data) {
this.data = data;
}
}
And this is the model of Data
public class Data {
public Data(){
this.a=0;
this.b="";
}
#Expose
public double a;
#Expose
public String b="";
public Double getA() {
return a;
}
public void setA(Double a) {
this.a = a;
}
public String getB() {
return b;
}
public void setB(String b) {
this.b = b;
}
}
Retrofit will convert the JSON into a Java object of type A. Now the next thing that I do with this object of type A is to convert it into another object of Type B.
For doing the conversion I used a utility class. Its description is below
This utility converts one java object to another java object.
* does not support Inheritance
* Mainly useful for when you have two Class one for Restful call and another for ORM suite
* if you get one object from RESTFUL call and another object to be created to save in ORM then generally we
* create another object and manually put the value by setter and getter
* just keep the same field name in both class and use this utility function to assign value from one to another
* and then use another to save in db. So no more stupid getter setter use, it handles nested class and Collection field .
Now the problem that I am facing is that this class throws an exception
Attempt to invoke virtual method 'java.lang.Class java.lang.Object.getClass()' on a null object reference
This is the only error that I get, nothing else. The stacktrace is clean, just this error. Basically the object conversion has failed. The reson is most likely because the data field is null because prior to that I was not getting any such error.
This is a portion of code from my utility class (the one responsible for object conversion). I pass the two object types one source and the other destination. The source in this case is object of type A. But the first line cause the exception that I have mentioned in the question.
// get the class of source object
Class sourceObjectType = sourceObject.getClass();
// get the class of destination object
Class destinationObjectType = destinationObject.getClass();
Now I am not sure how to handle this exception. I tried creating a constructor so that Data is not null but this isn't working. It would be great if someone can suggest something or help me out. Thanks !!
You can check first if your destinationObjectType is null using a simple if:
if(destinationObjectType){
// handle your condition here
}
or you can handle an exception:
try{
// get the class of source object
Class sourceObjectType = sourceObject.getClass();
// get the class of destination object
Class destinationObjectType = destinationObject.getClass();
}
catch(NullPointerException e){
// Handle your exception here
}
Regards!
I have following JSON. And I am parsing it using Jackson Parser
{
"code": 0,
"response": {
"pagination": {
"page": 1,
"limit": 20,
"count": 5,
"pageCount": 1
},
"random": [
....
]
}
}
Now I have simple POJO classes created for various random object. I expect 3-4 different types of random object. So instead of creating different wrapper classes for different types of 'random' object I created a generic one
EDITED CLASS:
public class PaginatedResponse<E> {
private Pagination pagination;
private List<E> responseList;
public Pagination getPagination() {
return pagination;
}
public void setPagination(Pagination pagination) {
this.pagination = pagination;
}
public List<E> getResponseList() {
return responseList;
}
public void setResponseList(List<E> responseList) {
this.responseList = responseList;
}
}
Now For mapping it I used,
JsonNode tree = mapper.readTree(response);
TypeReference<PaginatedResponse<LocationParent>> ref = new TypeReference<PaginatedResponse<LocationParent>>() { };
PaginatedResponse<LocationParent> resp = mapper.convertValue(tree.get("response"), ref);
But i am not able to map responseList. I get the pagination object but the responseList is always null. Now how to dynamically provide property name for responseList.
Please help
What you need for variable value type is handling for polymorphic types. Generic types alone won't help, since deserialization side would not know what type to use.
You can enable polymorphic type handling with annotation #JsonTypeInfo; but a problem in this particular case is that you want a List of things of arbitrary type -- due to type-erasure, all Java Lists are really just List<Object>; there is no typing for elements.
If it was me, I would probably sub-class PaginatedResponse and just add #JsonTypeInfo in base class, like:
#JsonTypeInfo(...) // see javadocs for properties needed
public abstract class PaginatedResponse<T> {
public Pagination pagination;
// .. and so on
}
public class PaginatedFooResponse<Foo> { }
The reason to use sub-classing here is simply make it possible for deserializer to figure out element type, given type of response object. Response object will have type (PaginatedFooResposne), and from that type of elements is available.
Try this::
JSONObject objJSON = JSONObject.fromString("urString");
String code = objJSON.get("code");