I have two classes:
public class ResponseInfo {
private final int code;
private final String description;
#JsonCreator
public static ResponseInfo of(
#JsonProperty("code") int code,
#JsonProperty("description") String description
) {
return new ResponseInfo(code, description);
}
private ResponseInfo(
int code,
String description
) {
this.code = code;
this.description = description;
}
#JsonProperty("code")
public int code() {
return code;
}
#JsonProperty("description")
public String description() {
return description;
}
}
and:
public class Response<T> {
private final ResponseInfo responseInfo;
private final T payload;
public static <T> Response<T> of(ResponseInfo responseInfo, T payload) {
return new Response<>(responseInfo, payload);
}
private Response(ResponseInfo responseInfo, T payload) {
this.responseInfo = responseInfo;
this.payload = payload;
}
#JsonUnwrapped
public ResponseInfo responseInfo() {
return responseInfo;
}
#JsonUnwrapped
public T payload() {
return payload;
}
}
I use them to add additional info into response (as code and description). For example:
Response.of(ResponseInfo.of(0, "OK"), User.of("Oleg", 23))
will be serialized into:
{
"age": 23,
"code": 0,
"description": "OK",
"name": "Oleg"
}
How the deserialization of Response can be done?
I can't use #JsonProperty in #JsonCreator directly cause I don't know properties of payload.
JsonCreator with #JsonUnwrapped also doesn't work.
I am using jackson-datatype-jdk8:2.9.5.
I've created implementation.
public class ResponseDeserializer
extends JsonDeserializer<Response<?>>
implements ContextualDeserializer {
private JavaType type;
#SuppressWarnings("unused")
public ResponseDeserializer() {
}
private ResponseDeserializer(JavaType type) {
this.type = type;
}
#Override
public JsonDeserializer<?> createContextual(
DeserializationContext context,
BeanProperty beanProperty
) {
JavaType contextualType = context.getContextualType();
if(contextualType == null) {
contextualType = beanProperty.getMember()
.getType();
}
if (!contextualType.isTypeOrSubTypeOf(Response.class)) {
throw new IllegalArgumentException("contextualType should be " + Response.class.getName());
}
final JavaType payloadType = contextualType.containedType(0);
return new ResponseDeserializer(payloadType);
}
#Override
public Response<?> deserialize(
JsonParser jsonParser,
DeserializationContext context
) throws IOException {
final ObjectCodec codec = jsonParser.getCodec();
JsonNode rootNode = codec.readTree(jsonParser);
final ResponseInfo responseInfo = ResponseInfo.of(
rootNode.get("code").asInt(),
rootNode.get("description").asText()
);
final JsonNode payloadNode = createPayloadNode(rootNode, codec);
final JsonParser payloadParser = payloadNode.traverse();
final Object payload = codec.readValue(payloadParser, type);
return Response.of(responseInfo, payload);
}
private JsonNode createPayloadNode(JsonNode rootNode, ObjectCodec codec) {
final Map<String, JsonNode> remainingNodes = findRemainingNodes(rootNode);
if(remainingNodes.size() == 1) {
final JsonNode payloadNode = remainingNodes.get("payload");
if(payloadNode != null && !payloadNode.isObject()) {
return payloadNode;
}
}
return buildRemainingNode(remainingNodes, codec);
}
private JsonNode buildRemainingNode(Map<String, JsonNode> remainingNodes, ObjectCodec codec) {
final ObjectNode remainingNode = (ObjectNode) codec.createObjectNode();
remainingNodes.forEach(remainingNode::set);
return remainingNode;
}
private Map<String, JsonNode> findRemainingNodes(JsonNode rootNode) {
Map<String, JsonNode> remainingNodes = new HashMap<>();
rootNode.fields()
.forEachRemaining(entry -> {
final String key = entry.getKey();
if(key.equals("code") || key.equals("description")) {
return;
}
remainingNodes.put(key, entry.getValue());
});
return remainingNodes;
}
}
Related
I have the following POJO that can be serialized into bytes or json.
public final class Message {
private final Data data;
private final Request request;
private final Response response;
public Message() {
this.data = new Data();
this.request = new Request();
this.response = new Response();
}
public Data getData() {
return data;
}
public Request getRequest() {
return request;
}
public Response getResponse() {
return response;
}
public Object query(String pointer) {
return toJson().query(pointer);
}
public byte[] toBytes() {
try {
return new ObjectMapper(new MessagePackFactory()).writeValueAsBytes(this);
} catch (JsonProcessingException ex) {
throw new MessageException(ex);
}
}
public JSONObject toJson() {
try {
return new JSONObject(new ObjectMapper().writeValueAsString(this));
} catch (JsonProcessingException ex) {
throw new MessageException(ex);
}
}
#Override
public String toString() {
try {
return toString(0);
} catch (MessageException ex) {
throw new MessageException(ex);
}
}
public String toString(int indent) {
try {
return toJson().toString(indent);
} catch (MessageException ex) {
throw new MessageException(ex);
}
}
}
Reference Classes:
public class Data {
private final Map<String, Map<String, Object>> dataMap;
public Data() {
this.dataMap = new HashMap();
}
public Data addToSet(String name, String key, Object value) {
Map<String, Object> map = dataMap.get(name);
if (map == null) {
map = new HashMap();
}
map.put(key, value);
dataMap.put(name, map);
return this;
}
public Map<String, Map<String, Object>> getSets() {
return dataMap;
}
public Data updateSet(String name, String key, Object value) {
return Data.this.addToSet(name, key, value);
}
public Data removeFromSet(String name, String key) {
Map<String, Object> map = dataMap.get(name);
if (map == null) {
throw new MessageException("No such property '" + key + "' for set '" + name + "'");
}
map.remove(key);
return this;
}
public Map<String, Object> getSet(String name) {
return dataMap.get(name);
}
}
public class Request {
private String method;
private String resource;
private final Map<String, Object> body;
private final Map<String, String> headers;
private final Map<String, String[]> parameters;
public Request() {
this.body = new HashMap();
this.headers = new HashMap();
this.parameters = new HashMap();
}
public String getMethod() {
return Objects.toString(method, "");
}
public String getResource() {
return Objects.toString(resource, "");
}
public Map<String, Object> getBody() {
return body;
}
public Map<String, String> getHeaders() {
return headers;
}
public Map<String, String[]> getParameters() {
return parameters;
}
public String getHeader(String name) {
return headers.get(name);
}
public Request setBody(String payload) {
try {
this.body.putAll(new ObjectMapper().readValue(payload, new TypeReference<Map<String, Object>>() {
}));
return this;
} catch (JsonProcessingException ex) {
throw new MessageException(ex);
}
}
public Request setMethod(String name) {
this.method = name;
return this;
}
public Request setResource(String name) {
this.resource = name;
return this;
}
public Request setHeaders(Map<String, String> headers) {
this.headers.putAll(headers);
return this;
}
public Request setParameters(Map<String, String[]> parameters) {
this.parameters.putAll(parameters);
return this;
}
}
public class Response {
private String code;
private String data;
private String messageId;
private String timestamp;
private String description;
public Response() {
}
public String getCode() {
return Objects.toString(code, "");
}
public String getData() {
return Objects.toString(data, "");
}
public String getMessageId() {
return Objects.toString(messageId, "");
}
public String getTimestamp() {
return Objects.toString(timestamp, "");
}
public String getDescription() {
return Objects.toString(description, "");
}
public Response setCode(String code) {
this.code = code;
return this;
}
public Response setData(String data) {
this.data = data;
return this;
}
public Response setMessageId(String messageId) {
this.messageId = messageId;
return this;
}
public Response setTimestamp(String timestamp) {
this.timestamp = timestamp;
return this;
}
public Response setDescription(String description) {
this.description = description;
return this;
}
}
When serializing to json I get a valid string
{
"request": {
"headers": {},
"method": "",
"resource": "",
"body": {
"whatsapp": {
"conversationId": "39f09c41-1bd3-4e81-b829-babed3747d4b",
"name": "Dave",
"source": "+123456789098"
},
"payment": {
"product": "chocolate",
"amount": 1,
"method": "cashapp",
"msisdn": "123456789098",
"entity": "The Fudge Shop"
}
},
"parameters": {}
},
"data": {
"sets": {
"whatsapp": {
"provider": "clickatell",
"name": "Dave",
"destination": "123456789098",
"source": "123456789098",
"message": "Your payment of $1.00 received, your receipt.no is QWJ124XPA9."
},
"cashapp": {
"amount": 1,
"receiptNo": "QWJ124XPA9",
"name": "Dave Chapelle",
"msisdn": "123456789098"
}
}
},
"response": {
"code": "202",
"data": "",
"messageId": "20210623160202a647d32ee9ae477f9c90d8b1fbfd763a",
"description": "Processing Request",
"timestamp": "2021-06-23 16:02:02.408"
}
}
When I attempt to deserialize the json back to a pojo
Message output = new ObjectMapper().readValue(json.toString(), Message.class);
I get the error :
com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize instance of `java.lang.String` out of START_OBJECT token
The error seems to be generated from the Request class when attempting to deserialize the Map<String, Object> body:
How may I deserialize the Map correctly?
For the String-Problem, these sources might help:
Can not deserialize instance of java.lang.String out of START_OBJECT token
https://www.baeldung.com/jackson-map#1-mapltstring-stringgt-deserialization
Why this code can't work
Jackson is not much more powerful than you are.
If Jackson gets an object to serialize, it tries to serialize all of its values. And only its values (which is pretty good for the independence from classes). This is a json object:
{
"type":"apple",
"quantity":3,
"imageID":17
}
Now, what is the class of this object? It could be Fruit.class, Image.class or even RoundObject.class, json doesn't know and Jackson neither.
So how does json find out what the class is? By looking at the type of the object reference. In your case, it's Object. In Object.class, Jackson cannot find a constructor that requires the variables of the object that has been saved, so it crashes.
Solution
Trying to serialize objects is not a good idea. If you have very different classes you want to put in, e.g. Apple and Banana, make an interface or abstract class called Fruit that both of them implement. Now, use this annotation at the top of this class:
#JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
property = "type") // name of the variable to save the kind of object you put in. NO VARIABLES in all classes that extend from Fruit are allowed to have this name (or at least #JsonProperty).
#JsonSubTypes({
#JsonSubTypes.Type(value = Apple.class, name = "banana"),
#JsonSubTypes.Type(value = Banana.class, name = "apple"),
})
And using a Map<String, Fruit> should work.
The solution that worked for me was using custom deserialization, #JsonDeserialize annotation & JsonDeserializer interface, in order to achieve the desired results.
Below is the solution:
public class Request {
private String method;
private String resource;
#JsonDeserialize(using = BodyDeserializer.class)
private final Map<String, Object> body;
private final Map<String, String> headers;
private final Map<String, String[]> parameters;
public Request() {
this.body = new HashMap();
this.headers = new HashMap();
this.parameters = new HashMap();
}
public String getMethod() {
return method;
}
public String getResource() {
return resource;
}
public Map<String, Object> getBody() {
return body;
}
public Map<String, String> getHeaders() {
return headers;
}
public Map<String, String[]> getParameters() {
return parameters;
}
public String getHeader(String name) {
return headers.get(name);
}
public Request setBody(Map<String, Object> body) {
this.body.putAll(body);
return this;
}
public Request setMethod(String name) {
this.method = name;
return this;
}
public Request setResource(String name) {
this.resource = name;
return this;
}
public Request setHeaders(Map<String, String> headers) {
this.headers.putAll(headers);
return this;
}
public Request setParameters(Map<String, String[]> parameters) {
this.parameters.putAll(parameters);
return this;
}
private static class BodyDeserializer extends JsonDeserializer<Map<String, Object>> {
#Override
public Map<String, Object> deserialize(JsonParser jp, DeserializationContext dc) throws IOException, JsonProcessingException {
JsonDeserializer<Object> deserializer = dc.findRootValueDeserializer(dc.constructType(Map.class));
Map<String, Object> map = (Map<String, Object>) deserializer.deserialize(jp, dc);
return map;
}
}
}
Try this one JacksonUtils
Message actual = createMessage();
String json = JsonUtils.prettyPrint().writeValue(actual);
System.out.println(json);
Message expected = JsonUtils.readValue(json, Message.class);
This is complete snippet:
public class MavenMain {
public static void main(String... args) {
Message actual = createMessage();
String json = JsonUtils.prettyPrint().writeValue(actual);
System.out.println(json);
Message expected = JsonUtils.readValue(json, Message.class);
}
private static Message createMessage() {
Message message = new Message();
message.setData(createData());
message.setRequest(createRequest());
message.setResponse(createResponse());
return message;
}
private static Data createData() {
Map<String, Object> whatsapp = new LinkedHashMap<>();
whatsapp.put("provider", "clickatell");
whatsapp.put("name", "Dave");
whatsapp.put("destination", "123456789098");
whatsapp.put("source", "123456789098");
whatsapp.put("message", "Your payment of $1.00 received, your receipt.no is QWJ124XPA9.");
Map<String, Object> cashapp = new LinkedHashMap<>();
cashapp.put("receiptNo", "QWJ124XPA9");
cashapp.put("name", "Dave Chapelle");
cashapp.put("msisdn", "123456789098");
Map<String, Map<String, Object>> dataMap = new LinkedHashMap<>();
dataMap.put("whatsapp", whatsapp);
dataMap.put("cashapp", cashapp);
Data data = new Data();
data.setDataMap(dataMap);
return data;
}
private static Request createRequest() {
Map<String, Object> whatsapp = new LinkedHashMap<>();
whatsapp.put("conversationId", "39f09c41-1bd3-4e81-b829-babed3747d4b");
whatsapp.put("name", "Dave");
whatsapp.put("source", "+123456789098");
Map<String, Object> payment = new LinkedHashMap<>();
payment.put("product", "chocolate");
payment.put("amount", 1);
payment.put("method", "cashapp");
payment.put("msisdn", "123456789098");
payment.put("entity", "The Fudge Shop");
Map<String, Object> body = new HashMap<>();
body.put("whatsapp", whatsapp);
body.put("payment", payment);
Request request = new Request();
request.setHeaders(Collections.emptyMap());
request.setMethod("");
request.setResource("");
request.setBody(body);
request.setParameters(Collections.emptyMap());
return request;
}
private static Response createResponse() {
Response response = new Response();
response.setCode("202");
response.setData("");
response.setMessageId("20210623160202a647d32ee9ae477f9c90d8b1fbfd763a");
response.setDescription("Processing Request");
response.setTimestamp("2021-06-23T16:02:02.408");
return response;
}
}
class Message {
private Data data;
private Request request;
private Response response;
public void setData(Data data) {
this.data = data;
}
public void setRequest(Request request) {
this.request = request;
}
public void setResponse(Response response) {
this.response = response;
}
}
class Data {
#JsonProperty("sets")
private Map<String, Map<String, Object>> dataMap;
public void setDataMap(Map<String, Map<String, Object>> dataMap) {
this.dataMap = dataMap;
}
}
class Request {
private String method;
private String resource;
private Map<String, Object> body;
private Map<String, String> headers;
private Map<String, String[]> parameters;
public void setMethod(String method) {
this.method = method;
}
public void setResource(String resource) {
this.resource = resource;
}
public void setBody(Map<String, Object> body) {
this.body = body;
}
public void setHeaders(Map<String, String> headers) {
this.headers = headers;
}
public void setParameters(Map<String, String[]> parameters) {
this.parameters = parameters;
}
}
class Response {
private String code;
private String data;
private String messageId;
private String timestamp;
private String description;
public void setCode(String code) {
this.code = code;
}
public void setData(String data) {
this.data = data;
}
public void setMessageId(String messageId) {
this.messageId = messageId;
}
public void setTimestamp(String timestamp) {
this.timestamp = timestamp;
}
public void setDescription(String description) {
this.description = description;
}
}
In case you want to use immutable object, then it's a bit another configuration of models, but code in the main class will be the same.
Depending on the content of a JSON file, I want to deserialize it either to a superclass or subclass.
It should be deserialized to the superclass if it looks like this:
{
"id":"123",
"title":"my title",
"body":"my body"
}
Or to the subclass if it looks like this:
{
"id":"123",
"title":"my title",
"body":"my body",
"tags":["tag1", "tag2"]
}
So the only difference is the tags array, which should be deserialized to a String array.
But if I trigger the deserialization in Jersey (Dropwizard) via POST request, it returns {"code":400,"message":"Unable to process JSON"}.
This is the superclass:
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME)
#JsonSubTypes({ #JsonSubTypes.Type(name = "subdocument", value = SubDocument.class) })
public class SuperDocument {
private String id;
private String title;
private String body;
public SuperDocument() {
}
#JsonCreator
public SuperDocument(#JsonProperty("id") String id, #JsonProperty("title") String title, #JsonProperty("body") String body) {
this.id = id;
this.title = title;
this.body = body;
}
#JsonProperty("id")
public String getId() {
return id;
}
#JsonProperty("id")
public void setId(String id) {
this.id = id;
}
... the other getters and setters ...
}
This is the subclass:
#JsonTypeName("subdocument")
public class SubDocument extends SuperDocument {
private String[] tags;
public SubDocument() {
}
#JsonCreator
public SubDocument(#JsonProperty("id") String id, #JsonProperty("title") String title, #JsonProperty("body") String body, #JsonProperty("tags") String[] tags) {
super(id, title, body);
this.tags = tags;
}
#JsonProperty("tags")
public String[] getTags() {
return tags;
}
#JsonProperty("tags")
public void setTags(String[] tags) {
this.tags = tags;
}
}
Do you know what I am doing wrong?
JsonTypeInfo require a property that can identify your sub-class/super class. For eg:
{
"id":"123",
"title":"my title",
"body":"my body",
"type":"superdocument"
}
and
{
"id":"123",
"title":"my title",
"body":"my body",
"tags":["tag1", "tag2"],
"type":"subdocument"
}
Then modify SuperDocument annotations as shown below.
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME,property="type")
#JsonSubTypes({ #JsonSubTypes.Type(name = "subdocument", value = SubDocument.class),#JsonSubTypes.Type(name = "superdocument", value = SuperDocument.class) })
public class SuperDocument {
}
If you don't want to intrduce an additional property "type", then you may have to write a custom type resolver and type deserializer as shown below.
public class DocumentTypeResolver extends StdTypeResolverBuilder {
#Override
public TypeDeserializer buildTypeDeserializer(
final DeserializationConfig config, final JavaType baseType, final Collection<NamedType> subtypes) {
return new DocumentDeserializer(baseType, null,
_typeProperty, _typeIdVisible, _defaultImpl);
}
}
Custom TypeDeserializer
public static class DocumentDeserializer extends AsPropertyTypeDeserializer {
public DocumentDeserializer(final JavaType bt, final TypeIdResolver idRes, final String typePropertyName, final boolean typeIdVisible, final Class<?> defaultImpl) {
super(bt, idRes, typePropertyName, typeIdVisible, defaultImpl);
}
public DocumentDeserializer(final AsPropertyTypeDeserializer src, final BeanProperty property) {
super(src, property);
}
#Override
public TypeDeserializer forProperty(final BeanProperty prop) {
return (prop == _property) ? this : new DocumentDeserializer(this, prop);
}
#Override
public Object deserializeTypedFromObject(final JsonParser jp, final DeserializationContext ctxt) throws IOException {
JsonNode node = jp.readValueAsTree();
Class<?> subType =null;
JsonNode tags = node.get("tags");
if (tags == null) {
subType=SuperDocument.class;
} else {
subType=SubDocument.class;
}
JavaType type = SimpleType.construct(subType);
JsonParser jsonParser = new TreeTraversingParser(node, jp.getCodec());
if (jsonParser.getCurrentToken() == null) {
jsonParser.nextToken();
}
JsonDeserializer<Object> deser = ctxt.findContextualValueDeserializer(type, _property);
return deser.deserialize(jsonParser, ctxt);
}
}
Now annotate your SuperDocument class as shown below
#JsonTypeInfo(use = JsonTypeInfo.Id.NONE)
#JsonTypeResolver(DocumentTypeResolver.class)
public class SuperDocument {
}
im using below code
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
WebTarget webTarget = client.target(uri);
Builder builder = webTarget.request();
Response response = builder.accept(MediaType.APPLICATION_JSON).get(Response.class);
final List<MyResponse> accountList = response.readEntity(new GenericType<List<MyResponse>>(){});
This returns accountList but all the values inside the list Objects were **null ie(Each property value inside MyResponse object is null)
But If i use below code
String myResponse = response
.readEntity(String.class);
ObjectMapper mapper = new ObjectMapper();
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
MyResponse[] obj = mapper.readValue(myResponse, MyResponse[].class);
obj returns the proper array of Objects..but i dont want to read as string and deserialize..Please suggest!
Uri response is as follows
[
{
"type": "A1",
"attrs": {
"test_card": "Y"
}
}, {
"type": "A2"
"attrs": {
"issue_card": "N"
}
}
]
MyResponse Object
#XmlRootElement
#XmlAccessorType(XmlAccessType.FIELD)
#JsonIgnoreProperties(ignoreUnknown = true)
public class MyResponse implements Serializable {
#JsonProperty("type")
private String Type;
#JsonProperty("attrs")
private MyAttributes myAttributes;
public MyAttributes getattrs() {
return myAttributes;
}
public void setattrs(MyAttributes myAttributes) {
this.myAttributes = myAttributes;
}
public MyResponse() {
}
public String gettype() {
return Type;
}
public void settype(String Type) {
this.Type = Type;
}
public int hashCode() {
return HashCodeBuilder.reflectionHashCode(this);
}
public boolean equals(Object obj) {
return EqualsBuilder.reflectionEquals(this, obj);
}
}
MyAttributes Object
#JsonIgnoreProperties(ignoreUnknown = true)
public class MyAttributes implements Serializable {
/**
*
*/
private static final long serialVersionUID = -4************;
#JsonProperty("test_card")
private String testCard;
public DecisionActionAttributes() {
}
public String getNewCardInd() {
return testCard;
}
public void setNewCardInd(String testCard) {
this.testCard = testCard;
}
#Override
public int hashCode() {
return HashCodeBuilder.reflectionHashCode(this, false);
}
#Override
public String toString() {
return ToStringBuilder.reflectionToString(this);
}
My Api response looks like this.
{
"status" : 1,
"message" : "Some Message",
"data" : {
}
}
My Response class looks like this. The type of data changes depending on the request being made.
public class Response<T>{
#Expose
#SerializedName("status")
private Integer status;
#Expose
#SerializedName("message")
private String message;
#Expose
#SerializedName("data")
private T data;
//Getters and Setters
}
Question 1. How to use gson to parse this json?
Response<ClassA> response = new Gson().fromJson(jsonString, ??);
Question 2. How would i write Parcelable implementation for this class.
dest.writeInt(status == null ? 0:status);
dest.writeString(message);
??
You need to do some changes in your Model structure. To do this..
Create a BaseDTO which is nothing but your Response class and extend your BaseDTO with your ClassA.
BaseDto.class
public class BaseDto {
#Expose
#SerializedName("status")
protected Integer status;
#Expose
#SerializedName("message")
protected String message;
public Integer getStatus() {
return status;
}
public void setStatus(Integer status) {
this.status = status;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
ClassA.class
public class ClassA extends BaseDto implements Parcelable {
String name;
protected ClassA(Parcel in) {
name = in.readString();
status = in.readByte() == 0x00 ? null : in.readInt();
message = in.readString();
}
#Override
public int describeContents() {
return 0;
}
#Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(name);
if (status == null) {
dest.writeByte((byte) (0x00));
} else {
dest.writeByte((byte) (0x01));
dest.writeInt(status);
}
dest.writeString(message);
}
#SuppressWarnings("unused")
public static final Parcelable.Creator<ClassA> CREATOR = new Parcelable.Creator<ClassA>() {
#Override
public ClassA createFromParcel(Parcel in) {
return new ClassA(in);
}
#Override
public ClassA[] newArray(int size) {
return new ClassA[size];
}
};
}
Use this site to generate Parcelable class http://www.parcelabler.com/
to parse using gson you just need to pass it's type to it..
I have written a function to simplify this..
Save these in a Util class
public static <T> String convertObjectToStringJson(T someObject, Type type) {
Gson gson = new Gson();
String strJson = gson.toJson(someObject, type);
return strJson;
}
public static <T> T getObjectFromJson(String json, Type type) {
Gson gson = new Gson();
if (json != null) {
if (json.isEmpty()) {
return null;
}
}
return gson.fromJson(json, type);
}
example to use these function:
ClassA classA = Util.getObjectFromJson(strJson, new TypeToken<ClassA>() {}.getType());
String jsonClassA = Util.convertObjectToStringJson(objClassA, new TypeToken<ClassA>() {}.getType());
Answering my own question.
Question 1:
Response<ClassA> response = new Gson().fromJson(jsonString, new TypeToken<Response<ClassA>>(){}.getType());
Question 2:
Changed class to this.
public class Response<T extends Parcelable>{
#Expose
#SerializedName("status")
private Integer status;
#Expose
#SerializedName("message")
private String message;
#Expose
#SerializedName("data")
private T data;
//Getters and Setters
#Override
public void writeToParcel(Parcel dest, int flags) {
if (data != null) {
dest.writeString(data.getClass().getName());
dest.writeParcelable(data, flags);
} else dest.writeString(null);
dest.writeString(message);
dest.writeInt(status);
}
}
protected Response(Parcel in) {
String className = in.readString();
if (className != null) {
try {
data = in.readParcelable(Class.forName(className).getClassLoader());
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
message = in.readString();
status = in.readInt();
}
I would like deserialize my custom serialized objects. My objects are basically consisting a simple Pair implementation.
class School{
Integer id;
String schoolName;
}
class Student{
Integer id;
Integer schoolId;
String studentName;
}
#JsonSerialize(using=PairSerializer.class)
public class Pair<V,K>{
V v;
K k;
}
Here is the result
[
{
"v":{
"id":1,
"schoolId":3,
"studentName":"O. Bas"
},
"k":{
"id":3,
"schoolName":"School 3"
}
},
{
"v":{
"id":2,
"schoolId":3,
"studentName":"C. Koc"
},
"k":{
"id":3,
"schoolName":"School 3"
}
}
]
v and k as field name in json is pretty ugly. That is why I have written a custom serializer as this:
#Override
public void serialize(Pair pair, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException, JsonProcessingException {
jsonGenerator.writeStartObject();
jsonGenerator.writeObjectField(CaseFormat.UPPER_CAMEL.to(CaseFormat.LOWER_CAMEL,pair.getK().getClass().getSimpleName() ), pair.getK());
jsonGenerator.writeObjectField(CaseFormat.UPPER_CAMEL.to(CaseFormat.LOWER_CAMEL,pair.getV().getClass().getSimpleName() ), pair.getV());
jsonGenerator.writeEndObject();
}
The result is exactly what I want. v and k field names are replaced by their class names.
[
{
"school":{
"id":3,
"schoolName":"School 3"
},
"student":{
"id":1,
"schoolId":3,
"studentName":"O. Bas"
}
},
{
"school":{
"id":3,
"schoolName":"School 3"
},
"student":{
"id":2,
"schoolId":3,
"studentName":"C. Koc"
}
}
]
Here is the my question. How can I deserialize my json string to List<Pair<V, K> ? The real problem is that V and K are depends on the deserialized context it might vary as Student, School or another pair implementation.
public class PairDeserializer extends JsonDeserializer<Pair> {
public PairDeserializer() {
}
#Override
public Pair deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {
// I need to Deserialized generic type information of Pair
}
}
I think, you should create your own PropertyNamingStrategy. For example see my simple implementation:
class MapTransformNamingStrategy extends LowerCaseWithUnderscoresStrategy {
private static final long serialVersionUID = 1L;
private Map<String, String> mapping;
public MapTransformNamingStrategy(Map<String, String> mapping) {
this.mapping = mapping;
}
#Override
public String translate(String property) {
if (mapping.containsKey(property)) {
return mapping.get(property);
}
return property;
}
}
Now you can use it in this way:
Map<String, String> mapping = new HashMap<String, String>();
mapping.put("k", "student");
mapping.put("v", "school");
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setPropertyNamingStrategy(new MapTransformNamingStrategy(mapping));
//etc
Example JSON output:
{ "school" : { "id" : 1,
"schoolName" : "The Best School in the world"
},
"student" : { "id" : 1,
"schoolId" : 1,
"studentName" : "Arnold Shwarz"
}
}
EDIT
Because my answer is not clear for everyone I present full example source code which serialize Java POJO objects into JSON and "vice versa".
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.PropertyNamingStrategy.LowerCaseWithUnderscoresStrategy;
public class JacksonProgram {
#SuppressWarnings("unchecked")
public static void main(String[] args) throws Exception {
List<Pair<Student, School>> pairs = createDataForSerialization();
Map<String, String> mapping = createSchoolStudentMapping();
JsonConverter jsonConverter = new JsonConverter(mapping);
String json = jsonConverter.toJson(pairs);
System.out.println("JSON which represents list of pairs:");
System.out.println(json);
List<Pair<Student, School>> value = jsonConverter.fromJson(json, List.class);
System.out.println("----");
System.out.println("Deserialized version:");
System.out.println(value);
}
private static Map<String, String> createSchoolStudentMapping() {
Map<String, String> mapping = new HashMap<String, String>();
mapping.put("k", "student");
mapping.put("v", "school");
return mapping;
}
private static List<Pair<Student, School>> createDataForSerialization() {
List<Pair<Student, School>> pairs = new ArrayList<Pair<Student, School>>();
pairs.add(new Pair<Student, School>(new Student(1, 3, "O. Bas"), new School(3, "School 3")));
pairs.add(new Pair<Student, School>(new Student(2, 4, "C. Koc"), new School(4, "School 4")));
return pairs;
}
}
class JsonConverter {
private Map<String, String> mapping;
private ObjectMapper objectMapper;
private JsonFactory jsonFactory;
public JsonConverter(Map<String, String> mapping) {
this.mapping = mapping;
initJsonObjects();
}
private void initJsonObjects() {
objectMapper = new ObjectMapper();
objectMapper.setPropertyNamingStrategy(new MapTransformNamingStrategy(mapping));
jsonFactory = new JsonFactory();
}
public String toJson(Object object) throws Exception {
StringWriter stringWriter = new StringWriter();
JsonGenerator jsonGenerator = jsonFactory.createGenerator(stringWriter);
objectMapper.writeValue(jsonGenerator, object);
return stringWriter.toString();
}
public <T> T fromJson(String json, Class<T> expectedType) throws Exception {
JsonParser jsonParser = jsonFactory.createJsonParser(json);
return objectMapper.readValue(jsonParser, expectedType);
}
}
class MapTransformNamingStrategy extends LowerCaseWithUnderscoresStrategy {
private static final long serialVersionUID = 1L;
private Map<String, String> mapping;
public MapTransformNamingStrategy(Map<String, String> mapping) {
this.mapping = mapping;
}
#Override
public String translate(String property) {
if (mapping.containsKey(property)) {
return mapping.get(property);
}
return property;
}
}
class School {
private Integer id;
private String schoolName;
public School() {
}
public School(Integer id, String schoolName) {
this.id = id;
this.schoolName = schoolName;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getSchoolName() {
return schoolName;
}
public void setSchoolName(String schoolName) {
this.schoolName = schoolName;
}
#Override
public String toString() {
return "School [id=" + id + ", schoolName=" + schoolName + "]";
}
}
class Student {
private Integer id;
private Integer schoolId;
private String studentName;
public Student() {
}
public Student(Integer id, Integer schoolId, String studentName) {
this.id = id;
this.schoolId = schoolId;
this.studentName = studentName;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public Integer getSchoolId() {
return schoolId;
}
public void setSchoolId(Integer schoolId) {
this.schoolId = schoolId;
}
public String getStudentName() {
return studentName;
}
public void setStudentName(String studentName) {
this.studentName = studentName;
}
#Override
public String toString() {
return "Student [id=" + id + ", schoolId=" + schoolId + ", studentName=" + studentName
+ "]";
}
}
class Pair<V, K> {
private V v;
private K k;
public Pair() {
}
public Pair(V v, K k) {
this.v = v;
this.k = k;
}
public V getV() {
return v;
}
public void setV(V v) {
this.v = v;
}
public K getK() {
return k;
}
public void setK(K k) {
this.k = k;
}
#Override
public String toString() {
return "Pair [v=" + v + ", k=" + k + "]";
}
}
The full output log:
JSON which represents list of pairs:
[{"school":{"id":1,"schoolId":3,"studentName":"O. Bas"},"student":{"id":3,"schoolName":"School 3"}},{"school":{"id":2,"schoolId":4,"studentName":"C. Koc"},"student":{"id":4,"schoolName":"School 4"}}]
----
Deserialized version:
[{school={id=1, schoolId=3, studentName=O. Bas}, student={id=3, schoolName=School 3}}, {school={id=2, schoolId=4, studentName=C. Koc}, student={id=4, schoolName=School 4}}]
Because the output JSON is not formatted I present it in more understandable version:
[
{
"school":{
"id":1,
"schoolId":3,
"studentName":"O. Bas"
},
"student":{
"id":3,
"schoolName":"School 3"
}
},
{
"school":{
"id":2,
"schoolId":4,
"studentName":"C. Koc"
},
"student":{
"id":4,
"schoolName":"School 4"
}
}
]
As you can see, we create new JsonConverter object with definition of mapping between Pair property names and which names we want to see in JSON string representation. Now if you have for example Pair<School, Room> you can create mapping Map in this way:
private static Map<String, String> createSchoolRoomMapping() {
Map<String, String> mapping = new HashMap<String, String>();
mapping.put("k", "school");
mapping.put("v", "room");
return mapping;
}
I was going for an answer with some annotation (JsonTypeInfo and JsonUnwrapped), but those two don't work well together apparently (see this issue). That would of handled both the serialization and deserialization part of your problem, without relying on custom de/serializer. Instead, you'll need a custom deserializer, which does something along those line:
class PairDeserializer extends JsonDeserializer<Pair>{
static Map<String, Class> MAPPINGS = new HashMap<String, Class>();
#Override
public Pair deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {
Object key = deserializeField(jp);
Object value = deserializeField(jp);
Pair pair = new Pair();
pair.k = key;
pair.v = value;
jp.nextToken();
return pair;
}
private Object deserializeField(JsonParser jp) throws IOException, JsonParseException, JsonProcessingException {
jp.nextValue();
String className = jp.getCurrentName();
return jp.readValueAs(MAPPINGS.get(className));
}
}
Then you only need to register the mappings you need