Jackson Array to Pojo Not Working Only on Android - java

I want to convert json array to POJO, it is working when running on JVM but failed on Android
This is my pojo:
package com.binance.api.client.domain.market;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
#JsonFormat(shape = JsonFormat.Shape.ARRAY)
#JsonPropertyOrder()
#JsonIgnoreProperties(ignoreUnknown = true)
public class Lilin {
public Long openTime;
public String open;
public String high;
public String low;
public String close;
public String volume;
public Long closeTime;
public String quoteAssetVolume;
public Long numberOfTrades;
public String takerBuyBaseAssetVolume;
public String takerBuyQuoteAssetVolume;
}
And then test it manually:
public void testCandlestickDeserializer() {
final String candlestickJson = "[\n" +
" 1499040000000,\n" +
" \"0.01634790\",\n" +
" \"0.80000000\",\n" +
" \"0.01575800\",\n" +
" \"0.01577100\",\n" +
" \"148976.11427815\",\n" +
" 1499644799999,\n" +
" \"2434.19055334\",\n" +
" 308,\n" +
" \"1756.87402397\",\n" +
" \"28.46694368\",\n" +
" \"17928899.62484339\"\n" +
" ]";
ObjectMapper mapper = new ObjectMapper();
try {
Lilin candlestick = mapper.readValue(candlestickJson, Lilin.class);
System.out.println(candlestick);
} catch (IOException e) {
System.err.println(e);
}
}
There is no error when try it on JVM but raises this error when run it on Android:
Cannot deserialize value of type `java.lang.Long` from String "0.01634790": not a valid Long value
it seems the #JsonPropertyOrder() annotation is not working properly on Android

It's possible that you might have missed to define the property ordering, e.g from the docs:
Examples:
// ensure that "id" and "name" are output before other properties
#JsonPropertyOrder({ "id", "name" })
// order any properties that don't have explicit setting using alphabetic order
#JsonPropertyOrder(alphabetic=true)
//This annotation may or may not have effect on deserialization: for basic JSON handling there is no effect, but for other supported data types (or structural conventions) there may be.
Source: https://fasterxml.github.io/jackson-annotations/javadoc/2.2.0/com/fasterxml/jackson/annotation/JsonPropertyOrder.html

Related

Does Jackson not pretty print values annotated by #JsonRawValue?

I store JSON in my database and want to include this JSON in an API response as-is, without de-serializing before serializing the data.
The data itself resides in a wrapper object. When serializing this wrapper, it appears the JSON from my database isn't pretty-printed alongside the rest of the data, giving really weird-looking responses.
I have written some example code to outline my issue:
import com.fasterxml.jackson.annotation.JsonRawValue;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
public class JacksonTest {
private static final String EXPECTED_OUTPUT = "{\n" +
" \"wrapper\" : {\n" +
" \"data\" : {\n" +
" \"raw\" : \"test\"\n" +
" }\n" +
" }\n" +
"}";
private static final String RAW_JSON = "{\n" +
" \"raw\" : \"test\"\n" +
"}";
static class Pojo {
#JsonRawValue
private final String data;
public Pojo(String data) {
this.data = data;
}
public String getData() {
return data;
}
}
static class Wrapper {
private final Pojo wrapper;
public Wrapper() {
wrapper = new Pojo(RAW_JSON);
}
#SuppressWarnings("unused")
public Pojo getWrapper() {
return wrapper;
}
}
#Test
void shouldEqual() throws JsonProcessingException {
ObjectMapper mapper = new ObjectMapper().enable(SerializationFeature.INDENT_OUTPUT);
String output = mapper.writeValueAsString(new Wrapper());
assertThat(output).isEqualTo(EXPECTED_OUTPUT);
}
}
This test fails with the following output:
{
"wrapper" : {
"data" : {
"raw" : "test"
}
}
}
While I expect jackson to give me the following output:
{
"wrapper" : {
"data" : {
"raw" : "test"
}
}
}
Is there any way to "fix" the indenting of the raw data that's annotated with #JsonRawValue?
Maybe with the following code your test will pass :
Object json = mapper.readValue(input, Object.class);
String indented = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(json);
You can check that stackoverflow question and its own answers from which the code I have written upper was coming from, from the accepted answer :
Convert JSON String to Pretty Print JSON output using Jackson

Apply a mask on a JSON to keep only mandatory data

I have an API which takes some potentially mandatory data to create a temporary session:
e.g.: at first, my POST /signup endpoint user need to send me the following data:
{
"customer": {
"age": 21,
"ssn": "000 00 0000",
"address": {
"street": "Customer St.",
"phone": "+66 444 333 222"
}
}
}
Let's call it JSON a.
On the other hand, I have some legal partners which requires some of these data, but not all of them:
e.g.:
{
"customer": {
"ssn": "The SSN is mandatory to register against XXX Company",
"address": {
"phone": "XXX Company will send a text message to validate your registration"
}
}
}
Let's call it JSON b.
Due to recent legal restrictions, in my information system, I have to keep only mandatory data for the user to carry with his chosen workflow.
Hence my question: is there a function (either built in Jackson or some other JSON handling library or an algorithm you would recommend) I could apply such that given JSON b and JSON a, it would output the following JSON:
{
"customer": {
"ssn": "000 00 0000",
"address": {
"phone": "+66 444 333 222"
}
}
}
Thinking a bit, I found something which might be a solution:
merge JSON a with JSON b, on conflict take JSON b values, name the result JSON c
make a diff (drop equals data) between JSON c and JSON a, on conflict take JSON a values
I know merging two JSON using Jackson can be done using com.fasterxml.jackson.databind.ObjectMapper#readerForUpdating, so my question could be reduced to: is there a way to make a diff between two JSON and give a conflict resolution function?
I'd suggest to use token/event/stream-based solution. The following is just an illustration using tiny parser/generator lib https://github.com/anatolygudkov/green-jelly (both Gson and Jackson also provide stream-oriented API):
import org.green.jelly.AppendableWriter;
import org.green.jelly.JsonEventPump;
import org.green.jelly.JsonNumber;
import org.green.jelly.JsonParser;
import java.io.StringWriter;
import java.io.Writer;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
public class FilterMyJson {
private static final String jsonToFilter = "{\n" +
" \"customer\": {\n" +
" \"age\": 21,\n" +
" \"ssn\": \"000 00 0000\",\n" +
" \"address\": {\n" +
" \"street\": \"Customer St.\",\n" +
" \"phone\": \"+66 444 333 222\"\n" +
" }\n" +
" }\n" +
"}";
public static void main(String[] args) {
final StringWriter result = new StringWriter();
final JsonParser parser = new JsonParser();
parser.setListener(new MyJsonFilter(result, "age", "street"));
parser.parse(jsonToFilter); // if you read a file with a buffer,
// call parse() several times part by part in a loop until EOF
parser.eoj(); // and then call .eoj()
System.out.println(result);
}
static class MyJsonFilter extends JsonEventPump {
private final Set<String> objectMembersToFilter;
private boolean currentObjectMemberIsAllowed;
MyJsonFilter(final Writer output, final String... objectMembersToFilter) {
super(new AppendableWriter<>(output));
this.objectMembersToFilter = new HashSet<>(Arrays.asList(objectMembersToFilter));
}
#Override
public boolean onObjectMember(final CharSequence name) {
currentObjectMemberIsAllowed =
!objectMembersToFilter.contains(name.toString());
return super.onObjectMember(name);
}
#Override
public boolean onStringValue(final CharSequence data) {
if (!currentObjectMemberIsAllowed) {
return true;
}
return super.onStringValue(data);
}
#Override
public boolean onNumberValue(final JsonNumber number) {
if (!currentObjectMemberIsAllowed) {
return true;
}
return super.onNumberValue(number);
}
}
}
prints:
{
"customer":
{
"ssn": "000 00 0000",
"address":
{
"phone": "+66 444 333 222"
}
}
}
The code is quite simplified. For now it filters out only string and number scalars. No object hierarchy is supported. You may need to improve the code for some cases therefore.
Props of such type of solution:
the file/data doesn't require to be loaded entirely into memory, you
can process megs/gigs with no problems
it works much more faster, especially for large files
it's easy to implement any custom type/rule of transformation with this pattern. For example, it is easy to have your filter to be parametrized, you don't have to recompile the code each time your data structure is changed
Thanks to https://github.com/algesten/jsondiff which gave me the keywords to find a more maintained https://github.com/java-json-tools/json-patch (ability to use your own ObjectMapper), I have found my answer (see mask() method):
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
import com.fasterxml.jackson.annotation.JsonMerge;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.github.fge.jsonpatch.diff.JsonDiff;
import java.math.BigInteger;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.SneakyThrows;
import lombok.ToString;
import org.junit.jupiter.api.Test;
class JsonPatchTest {
private static final ObjectMapper mapper = new ObjectMapper();
#Getter
#Builder
#ToString
#EqualsAndHashCode
#NoArgsConstructor(access = AccessLevel.PRIVATE)
#AllArgsConstructor(access = AccessLevel.PRIVATE)
public static class Data {
#JsonMerge Customer customer;
#Getter
#Builder
#ToString
#EqualsAndHashCode
#NoArgsConstructor(access = AccessLevel.PRIVATE)
#AllArgsConstructor(access = AccessLevel.PRIVATE)
public static class Customer {
#JsonMerge BigInteger age;
#JsonMerge String ssn;
#JsonMerge Address address;
#Getter
#Builder
#ToString
#EqualsAndHashCode
#NoArgsConstructor(access = AccessLevel.PRIVATE)
#AllArgsConstructor(access = AccessLevel.PRIVATE)
public static class Address {
#JsonMerge String street;
#JsonMerge String phone;
}
}
#SneakyThrows
Data merge(Data parent) {
var originCopyAsString = mapper.writerFor(this.getClass()).writeValueAsString(this);
var parentAsString = mapper.writerFor(this.getClass()).writeValueAsString(parent);
var parentCopy = mapper.readerFor(this.getClass()).readValue(parentAsString);
var clone = mapper.readerForUpdating(parentCopy).readValue(originCopyAsString);
return (Data) clone;
}
}
#SneakyThrows
#Test
void mask() {
final var diff = JsonDiff.asJsonPatch(mapper.readTree(jsonC()), mapper.readTree(jsonB()));
final var masked = diff.apply(mapper.readTree(jsonA())).toPrettyString();
assertThat(masked).isEqualToIgnoringWhitespace(masked());
}
private String jsonA() {
return "{\n"
+ " \"customer\": {\n"
+ " \"age\": 21,\n"
+ " \"ssn\": \"000 00 0000\",\n"
+ " \"address\": {\n"
+ " \"street\": \"Customer St.\",\n"
+ " \"phone\": \"+66 444 333 222\"\n"
+ " }\n"
+ " }\n"
+ "}";
}
private String jsonB() {
return "{\n"
+ " \"customer\": {\n"
+ " \"ssn\": \"The SSN is mandatory to register against XXX Company\",\n"
+ " \"address\": {\n"
+ " \"phone\": \"XXX Company will send a text message to validate your registration\"\n"
+ " }\n"
+ " }\n"
+ "}";
}
#SneakyThrows
private String jsonC() {
final Data dataA = mapper.readerFor(Data.class).readValue(jsonA());
final Data dataB = mapper.readerFor(Data.class).readValue(jsonB());
final Data merged = dataB.merge(dataA);
return mapper.writerFor(Data.class).writeValueAsString(merged);
}
#SneakyThrows
private String masked() {
return "{\n"
+ " \"customer\": {\n"
+ " \"ssn\": \"000 00 0000\",\n"
+ " \"address\": {\n"
+ " \"phone\": \"+66 444 333 222\"\n"
+ " }\n"
+ " }\n"
+ "}";
}
}

Unable to deserialise via mixin

Attached is my json. I am receiving this from an endpoint. I have the object type in my consuming app. the object contains; success, message and loggedInMember (which is an object). In the json, the feeClasses contains a lot of fields and objects etc which is why i have not included the whole json. I am only interested in the success and message fields.
{
"header":{
"messageId":null,
"receivedTimestamp":1611246394839,
"replyTo":null,
"redelivered":false,
"originator":null
},
"internalId":null,
"auditPoints":[
],
"success":true,
"message":"",
"loggedInMember":{
"feeClasses":{
...CONTAINS A LOT OF FIELDS...
}
}
}
I am trying to map this response to the object type, hence essentially leaving loggedInMembers null. This is my test I am trying to run:
public void test() throws JsonProcessingException
{
//String json = "{\"header\":{\"messageId\":null,\"bucketNumber\":null,\"senderSubId\":null,\"senderLocationId\":null,\"onBehalfOfCompId\":null,\"onBehalfOfSubId\":null,\"onBehalfOfLocationId\":null,\"correlationId\":null,\"receivedTimestamp\":1611246394839,\"replyTo\":null,\"redelivered\":false,\"originator\":null},\"internalId\":null,\"auditPoints\":[],\"success\":true,\"message\":\"\",\"loggedInMember\":{\"memberLoginName\":\"BMARTINTEST\",\"memberId\":\"201901241246290000036402D\",\"settlementAccountIds\":[\"201901241246290000036491D\"],\"parentMemberId\":\"1\",\"firmId\":\"990\",\"memberType\":\"INDIVIDUAL\",\"memberAccountType\":\"PROD\",\"password\":\"D1208B304FD7AA6187690A389A5040C1D9B07643\",\"feeClasses\":{\"byId\":{\"201902120947520000559606D\":{\"memberLoginName\":\"BMARTINTEST\",\"feeClassId\":\"201508041827550000942152D\",\"memberFeeClassId\":\"201902120947520000559606D\",\"allocatedDate\":{\"year\":2019,\"month\":2,\"day\":12,\"timeMillis\":1549929600000},\"firstUsedForTradeDate\":{\"year\":2019,\"month\":2,\"day\":12,\"timeMillis\":1549929600000},\"firstUsedForSettlementDate\":null,\"usableFromDate\":{\"year\":2019,\"month\":2,\"day\":12,\"timeMillis\":1549929600000},\"usableToDate\":{\"year\":2019,\"month\":2,\"day\":19,\"timeMillis\":1550534400000},\"usableToTimestamp\":1550613600000,\"usableBusinessDaysAllocated\":6,\"usableBusinessDaysRemaining\":0,\"narrative\":\"Bonus assigned to member at first-time funding of amount 4000.00 : Set expiration date/time\",\"disabled\":false,\"usableForTrade\":true,\"usableForSettlement\":true},\"202001290940390000868824D\":{\"memberLoginName\":\"BMARTINTEST\",\"feeClassId\":\"202001290940340000776406D\",\"memberFeeClassId\":\"202001290940390000868824D\",\"allocatedDate\":{\"year\":2020,\"month\":1,\"day\":29,\"timeMillis\":1580256000000},\"firstUsedForTradeDate\":null,\"firstUsedForSettlementDate\":null,\"usableFromDate\":{\"year\":2020,\"month\":1,\"day\":6,\"timeMillis\":1578268800000},\"usableToDate\":{\"year\":2020,\"month\":2,\"day\":27,\"timeMillis\":1582761600000},\"usableToTimestamp\":1582840800000,\"usableBusinessDaysAllocated\":0,\"usableBusinessDaysRemaining\":0,\"narrative\":\"Added NO_FEES_CLASS\",\"disabled\":false,\"usableForTrade\":true,\"usableForSettlement\":true},\"201901241246290000036417D\":{\"memberLoginName\":\"BMARTINTEST\",\"feeClassId\":\"201508041736360000943781D\",\"memberFeeClassId\":\"201901241246290000036417D\",\"allocatedDate\":{\"year\":2019,\"month\":1,\"day\":24,\"timeMillis\":1548288000000},\"firstUsedForTradeDate\":null,\"firstUsedForSettlementDate\":null,\"usableFromDate\":{\"year\":2019,\"month\":1,\"day\":24,\"timeMillis\":1548288000000},\"usableToDate\":null,\"usableToTimestamp\":null,\"usableBusinessDaysAllocated\":0,\"usableBusinessDaysRemaining\":0,\"narrative\":null,\"disabled\":false,\"usableForTrade\":true,\"usableForSettlement\":true}},\"empty\":false},\"legalName\":\"Martin Birch\",\"taxId\":\"345335454\",\"taxCountryId\":\"US\",\"currency\":\"USD\",\"lastTradeId\":null,\"introducingBrokerMemberId\":null,\"introducingBrokerMemberName\":null,\"introducingBrokerMemberCode\":null,\"clearedByMemberId\":\"SECOND_TEST\",\"clearedByMemberLoginName\":null,\"memberProblems\":[],\"emailNotificationEnabled\":true,\"rtafLevelId\":0,\"rtafAmount\":0,\"maxNumberOfPositionAccounts\":1,\"ciciIdentifier\":null,\"traderRequired\":false,\"interestClass\":\"INDIVIDUAL\",\"memberCreatedDate\":1548333989000,\"parentMemberLoginNames\":[\"NADEX.COM\",\"NADEX\"],\"demoStartDate\":null,\"demoEndDate\":null,\"clientIdMaxLimit\":null,\"memberAccountApplicationFieldData\":null,\"rank\":0,\"uuid\":\"201901241246290000036395D\",\"referrerId\":\"raf4qam5h00s36d\",\"testMember\":false},\"allReplyToSource\":[],\"sendToOriginatorOnly\":false}";
String json = "{\n" +
" \"header\":{\n" +
" \"messageId\":null,\n" +
" \"receivedTimestamp\":1611246394839,\n" +
" \"replyTo\":null,\n" +
" \"redelivered\":false,\n" +
" \"originator\":null\n" +
" },\n" +
" \"internalId\":null,\n" +
" \"auditPoints\":[\n" +
" \n" +
" ],\n" +
" \"success\":true,\n" +
" \"message\":\"\",\n" +
" \"loggedInMember\":{\n" +
" \"feeClasses\":{\n" +
" \n" +
" }\n" +
" }\n" +
"}";
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
objectMapper.addMixIn(LogonResponseMessage.class, LogonResponseMixin.class);
LogonResponseMessage responseMessage = objectMapper.readValue(json, LogonResponseMessage.class);
System.out.println(responseMessage);
}
My mixin:
public abstract class LogonResponseMixin
{
LogonResponseMixin(#JsonProperty("success") boolean success, #JsonProperty("message") String message){};
#JsonIgnore
abstract Member loggedInMember();
#JsonIgnore
abstract MemberFeeClasses feeClasses();
#JsonIgnore
abstract Header header();
}
I am getting the following error: com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of domain.xyz.MemberFeeClasses (no Creators, like default constructor, exist): cannot deserialize from Object value (no delegate- or property-based Creator)
Am i creating the mixin wrong? I have asked in a previous question and using mixin was the general consensus but it doesn't seem to play ball with me.
Thank you.
The reason why MemberFeeClasses cannot be constructed is the same as your initial problem, just add a mixin for all classes
See the example below:
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.Getter;
import lombok.Setter;
import java.io.IOException;
public class Test {
//------------------------------------------------//
// PART 1 - MAIN TEST METHOD
//------------------------------------------------//
public static void main(String[] args) throws IOException {
String json = "{\n" +
" \"header\":{" +
" },\n" +
" \"success\":true,\n" +
" \"message\":\"\",\n" +
" \"loggedInMember\":{\n" +
" \"feeClasses\":{\n" +
" \"amount\": \"20\"\n" +
" }\n" +
" }\n" +
"}";
ObjectMapper objectMapper = new ObjectMapper();
//objectMapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
objectMapper.addMixIn(LogonResponseMessage.class, LogonResponseMixin.class);
objectMapper.addMixIn(Member.class, MemberMixin.class);
objectMapper.addMixIn(MemberFee.class, MemberFeeMixin.class);
LogonResponseMessage responseMessage = objectMapper.readValue(json, LogonResponseMessage.class);
System.out.println(responseMessage.loggedInMember.feeClasses.amount);
}
//------------------------------------------------//
// PART 2 - CREATOR MIXIN
//------------------------------------------------//
static abstract class LogonResponseMixin {
LogonResponseMixin(#JsonProperty("success") boolean success,
#JsonProperty("message") String message,
#JsonProperty("header") Header header,
#JsonProperty("loggedInMember") Member member) {
}
}
static abstract class MemberMixin {
MemberMixin(#JsonProperty("feeClasses") MemberFee feeClasses) {
}
}
static abstract class MemberFeeMixin {
#JsonCreator
MemberFeeMixin(#JsonProperty("amount") String amount) {
}
}
//------------------------------------------------//
// PART 3 - EXAMPLE CLASS DEFINITION
//------------------------------------------------//
static class Header {
}
#Getter
#Setter
static class Member {
private MemberFee feeClasses;
public Member(MemberFee feeClasses) {
this.feeClasses = feeClasses;
}
}
#Getter
#Setter
static class MemberFee {
private String amount;
public MemberFee(String amount) {
this.amount = amount;
}
}
#Getter
#Setter
static class LogonResponseMessage {
private boolean success;
private String message;
private Header header;
private Member loggedInMember;
public LogonResponseMessage(boolean success, String message, Header header, Member member) {
this.success = success;
this.message = message;
this.header = header;
this.loggedInMember = member;
}
}
}

Using Gson fromJson method not working

I have a json string that is represented as below:
"{"RequestId":255,
"RequestTime":"2016-04-08T17:00:40.327",
"Otp":"123456",
"AppName":"This is my app name",
"IsAwaitingResponse":false}"
I also have a class object below that I wish to create using the Gson library fromJson() method...
public class AccessRequest{
public int Id;
public String RequestTime;
public String Otp;
public String AppName;
public Boolean IsAwaitingResponse;
}
Now when I call the gson.fromJson method it does not error but my object is not getting set. This is the code I'm using...
Gson gson = new Gson();
AccessRequest ar;
ar = gson.fromJson(jsonString, AccessRequest.class);
It is instead setting my 'ar' variable to some strange gson object that clearly is wrong but no error is thrown.
{serializeNulls:falsefactories:[Factory[typeHierarchy=com.google.gson.JsonElement,adapter=com.google.gson.internal.bind.TypeAdapters$25#e8dad0a],
From what I can see, there is no error in my Json string ( although I am new to json ) , so I'm not really sure why this method wouldn't be working...
You probably printed the gson object instead of the ar object, as #Pillar noted in the comments.
I've tried your example and it works as expected. Also, the Id field is not being set because the name of the property does not match. You should use #SerializedName or change any of the names. This has also been noted in the comments.
This is the working example:
package net.sargue.gson;
import com.google.gson.Gson;
import org.intellij.lang.annotations.Language;
public class SO36553536 {
public static void main(String[] args) {
#Language("JSON")
String json = "{\n" +
" \"RequestId\": 255,\n" +
" \"RequestTime\": \"2016-04-08T17:00:40.327\",\n" +
" \"Otp\": \"123456\",\n" +
" \"AppName\": \"This is my app name\",\n" +
" \"IsAwaitingResponse\": false\n" +
"}";
Gson gson = new Gson();
AccessRequest ar;
ar = gson.fromJson(json, AccessRequest.class);
System.out.println("ar.getClass() = " + ar.getClass());
System.out.println("ar = " + ar);
}
public class AccessRequest {
public int Id;
public String RequestTime;
public String Otp;
public String AppName;
public Boolean IsAwaitingResponse;
#Override
public String toString() {
return "AccessRequest{" + "Id=" + Id +
", RequestTime='" + RequestTime + '\'' +
", Otp='" + Otp + '\'' +
", AppName='" + AppName + '\'' +
", IsAwaitingResponse=" + IsAwaitingResponse +
'}';
}
}
}
And this is the execution output:
ar.getClass() = class net.sargue.gson.SO36553536$AccessRequest
ar = AccessRequest{Id=0
, RequestTime='2016-04-08T17:00:40.327'
, Otp='123456'
, AppName='This is my app name'
, IsAwaitingResponse=false}
As just need to change RequestId from Id class AccessRequest.
you will get proper output.

How can i deserialize Json string where object's field's subclass is included in json string, using Java and Jackson library

I am very new to Java. I have some classes Site, Instances, CloudInstance. Class Site has an attribute instances and class CloudInstance inherits class Instance. They are as follows-
public class Site extends BaseEntity {
private String siteName;
List<Instance> instances = Lists.newArrayList();
}
public class Instance extends BaseEntity {
private String instanceId;
private String name;
}
public class CloudInstance extends Instance {
private String availabilityZone;
private String instanceType
}
I am deserializing json string as follows -
import com.fasterxml.jackson.databind.ObjectMapper;
ObjectMapper mapper = new ObjectMapper();
BaseEntity obj = null;
obj = (BaseEntity) mapper.readValue(jsonStr, Site.class);
It works fine if my jsonStr does not contain fields of class 'CloudInstance' and contains field instance with Instance class's fields.
Problem - Now i want to deserialize the jsonStr which includes 'CloudInstance' classe's fiels as well as the part of 'instances' field of class 'Site'. Ex jsonStr is as follows -
{
"id": null,
"siteName": "demo",
"instances": [
{
"instanceId": "i-8c2ee5fc",
"name": "some-node",
"availabilityZone": "some-zone",
"instanceType": "t1.micro"
}]
}
For the above jsonStr i get following error
error: Unrecognized field \"availabilityZone\" and error: Unrecognized field \"instanceType\"
With lots of if else and dirty code i can get the obj of Site including above fields. But i want to implement clean solution for this.
Is there any library which can do this? Any help id valuable. Please help..!!
Thanks in advance.
What you are trying to achieve is called polymorphic deserialization. Your example fails because Jackson needs to know what instance type should be constructed from JSON and placed to the list of instances. Please refer to this wiki page for detailed explanation.
I have modified you example to demonstrate how it could work. I've added the instance type information in the #type field in the JSON representation. Also I've made all the classes immutable using constructors annotated with the #JsonCreator annotation to create instances.
public class JacksonPolymorphism {
public static class BaseEntity {
private final String id;
protected BaseEntity(String id) {
this.id = id;
}
}
public static class Site extends BaseEntity {
private final String siteName;
private final List<Instance> instances;
#JsonCreator
public Site(#JsonProperty("id") String id,
#JsonProperty("siteName") String siteName,
#JsonProperty("instances") List<Instance> instances) {
super(id);
this.siteName = siteName;
this.instances = instances;
}
#Override
public String toString() {
return "Site{" +
"siteName='" + siteName + '\'' +
", instances=" + instances +
'}';
}
}
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.PROPERTY,
property = "#type")
#JsonTypeName(value = "simple")
public static class Instance extends BaseEntity {
private final String name;
#JsonCreator
public Instance(#JsonProperty("instanceId") String id,
#JsonProperty("name") String name) {
super(id);
this.name = name;
}
#Override
public String toString() {
return "Instance{" +
"name='" + name + '\'' +
'}';
}
}
#JsonTypeName("cloud")
public static class CloudInstance extends Instance {
private final String availabilityZone;
private final String instanceType;
public CloudInstance(#JsonProperty("instanceId") String id,
#JsonProperty("name") String name,
#JsonProperty("availabilityZone") String availabilityZone,
#JsonProperty("instanceType") String instanceType) {
super(id, name);
this.availabilityZone = availabilityZone;
this.instanceType = instanceType;
}
#Override
public String toString() {
return "CloudInstance{" +
"availabilityZone='" + availabilityZone + '\'' +
", instanceType='" + instanceType + '\'' +
'}';
}
}
public static final String JSON = "{\n" +
" \"id\": null,\n" +
" \"siteName\": \"demo\",\n" +
" \"instances\": [\n" +
" {\n" +
" \"#type\": \"cloud\",\n" +
" \"instanceId\": \"i-8c2ee5fc\",\n" +
" \"name\": \"some-node\",\n" +
" \"availabilityZone\": \"some-zone\",\n" +
" \"instanceType\": \"t1.micro\" \n" +
" }," +
" {\n" +
" \"#type\": \"simple\",\n" +
" \"instanceId\": \"ABC\",\n" +
" \"name\": \"FGF\"\n" +
" }]" +
" }";
public static void main(String[] args) throws IOException {
ObjectMapper mapper = new ObjectMapper();
mapper.registerSubtypes(CloudInstance.class);
System.out.println(mapper.readValue(JSON, Site.class));
}
}
Output:
Site{siteName='demo', instances=[CloudInstance{availabilityZone='some-zone', instanceType='t1.micro'}, Instance{name='FGF'}]}
I always had problems to deserialize JSON that contains List<...> objects with Jackson, so try to deserialize with Gson:
https://code.google.com/p/google-gson/
Take a look at the documentation in the methods fromJson and toJson.
I hope that can help,
Best regards
Never had consistent luck with Jackson or Gson, try Flex JSON instead:
JSONSerializer ser = new JSONSerializer();
String json = ser.deepSerialize(yourObject);
JSONDeserializer<YourMainType> der = new JSONDeserializer<YourMainType>();
YourMainType mainType = der.deserialize(json);
For this to work, all classes subject to serialization/deserialization must expose getters/setters consistent with Java Beans convention.
You can used GSON Library for de-serialize your Json into your class object.
There is function gson.fromJson(JSON String) which convert json string to class object.
Here is Sample code :
Gson json = new Gson();
Site site = json.fromJson(jsonStr, Site.class);
But in your code you have replace
List<Instance> instances = Lists.newArrayList();
this line in class Site with
List<CloudInstance> instances = new ArrayList<CloudInstance>();
Because your CloudInstance class extend Instance it means CloudInstance class include member of Instance class. As per json you need to do this to cast directly into class object.
May this will help you.

Categories

Resources