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
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;
}
}
}
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
Jackson has ability to skip unknow properties globally using DeserializationFeature, but I can't find any global config to ignore whole class from being parsed. I have class with two methods with the same name but with different arguments, so I want to set this class as ignorable globally(using objectMapper object) not just by adding any annotations to model class. May be someone faced with such problem.
Sorry for bad English.
One option is to mark the type you wish to ignore with #JsonIgnoreType annotation. If you don't want to mess your model with Jackson annotations you can use mix-ins.
Another option is to override the Jackson annotation introspector to ignore the property based on its type.
Here is example shows both:
public class JacksonIgnoreByType {
public static final String JSON = "{\n" +
" \"bean1\" : {\n" +
" \"field1\" : \"value1\"\n" +
" },\n" +
" \"bean2\" : {\n" +
" \"field2\" : \"value2\"\n" +
" },\n" +
" \"bean3\" : {\n" +
" \"field3\" : \"value3\"\n" +
" }\n" +
"}\n";
public static class Bean1 {
public String field1;
#Override
public String toString() {
return "Bean1{" +
"field1='" + field1 + '\'' +
'}';
}
}
#JsonIgnoreType
public static class Bean2 {
public String field2;
}
public static class Bean3 {
public String field3;
}
public static class Bean4 {
public Bean1 bean1;
public Bean2 bean2;
public Bean3 bean3;
#Override
public String toString() {
return "Bean4{" +
"bean1=" + bean1 +
", bean2=" + bean2 +
", bean3=" + bean3 +
'}';
}
}
public static void main(String[] args) throws IOException {
ObjectMapper mapper = new ObjectMapper();
mapper.setAnnotationIntrospector(new JacksonAnnotationIntrospector(){
#Override
public boolean hasIgnoreMarker(AnnotatedMember m) {
return m.getRawType() == Bean3.class || super.hasIgnoreMarker(m);
}
});
System.out.println(mapper.readValue(JSON, Bean4.class));
}
}
Output:
Bean4{bean1=Bean1{field1='value1'}, bean2=null, bean3=null}
I'm trying to use GSON to deserialize some Json to a nice, tidy object. Now, I have managed to get the Json to correctly map to some of the more obvious variables. However, while trying to map some of the Json I came across this:
{
"this_number": 1,
"that_number": 12,
"some_string": "String!",
"list_of_objects": {
"342356676784653234535345": {
"random_double": "0.1235667456456",
"magic": "29",
"health": 1,
"price": 7,
"point": {
"x": 2,
"y": 70
}
},
"2345263767467354": {
"random_double": "0.1235667456456",
"magic": "23",
"health": 1,
"price": 9,
"point": {
"x": 0,
"y": 70
}
}
}
}
It was mapping nicely until I came to "list_of_objects". I can't for the life of me work out how to implement it. I think the main issue is that they are no longer static class names, they are randomized. Therefore it would be totally impractical (and impossible) to write something like:
class 342356676784653234535345{
double random_double = 0.0;
//etc
}
I've had a look around Stackoverflow, but the answers seem quite complex and many don't quite answer what I'm wanting to know.
I have played around with the plain Object method used here, but I couldn't find any further information on its usage.
I also keep finding references to mapping to generic types, but I don't quite understand what is going on. For example
You can convert a JSON string to an equivalent Java object using custom Gson JsonDeserializer
Assuming you have mapping classes
public class Data {
private int this_number;
private int that_number;
private String some_string;
private List<DataInfo> objects;
}
public class DataInfo {
private double random_double;
private int magic;
private int health;
private int price;
}
public class Point {
int x ;
int y;
}
CustomDeserializer
public class CustomDeserializer implements JsonDeserializer<Data> {
#Override
public Data deserialize(final JsonElement json, final Type typeOfT, final JsonDeserializationContext context) throws JsonParseException {
final JsonObject jsonObject = json.getAsJsonObject();
final int this_number = jsonObject.get("this_number").getAsInt();
final int that_number = jsonObject.get("that_number").getAsInt();
final String some_string = jsonObject.get("some_string").getAsString();
JsonObject list_of_objects =jsonObject.get("list_of_objects").getAsJsonObject();
Set<Entry<String, JsonElement>> objects = list_of_objects.entrySet();
final Data data = new Data();
List<DataInfo> list = new ArrayList<>();
Gson gson = new Gson();
for (Entry<String, JsonElement> entry : objects) {
JsonElement jsonElement = entry.getValue();
DataInfo info = gson.fromJson(jsonElement,DataInfo.class);
list.add(info);
}
data.setObjects(list);
data.setSome_string(some_string);
data.setThat_number(that_number);
data.setThis_number(this_number);
return data;
}
}
Just define
Map<String, Inner> list_of_objects;
in your outer class and let Gson do the work for you. It will deserialize nicely without effort. To make things clearer I did a full example based on your data. Just copy/paste/build/run this class. Your data structures are defined as static inner classes for my convenience, you can put them into separate files.
package stackoverflow.questions.q23472175;
import java.util.Map;
import com.google.gson.Gson;
public class Q23472175 {
private static class Point {
int x;
int y;
#Override
public String toString() {
return "Point [x=" + x + ", y=" + y + "]";
}
}
private static class Inner {
String random_double;
String magic;
int health;
int price;
Point point;
#Override
public String toString() {
return "Inner [random_double=" + random_double + ", magic=" + magic + ", health=" + health + ", price=" + price + ", point=" + point + "]";
}
}
private static class Outer {
int this_number;
int that_number;
String some_string;
Map<String, Inner> list_of_objects;
#Override
public String toString() {
return "Outer [this_number=" + this_number + ", that_number=" + that_number + ", some_string=" + some_string + ", list_of_objects=" + list_of_objects + "]";
}
}
public static void main(String[] args) {
String json = "{"+
" \"this_number\": 1,"+
" \"that_number\": 12,"+
" \"some_string\": \"String!\","+
" \"list_of_objects\": {"+
" \"342356676784653234535345\": {"+
" \"random_double\": \"0.1235667456456\","+
" \"magic\": \"29\","+
" \"health\": 1,"+
" \"price\": 7,"+
" \"point\": {"+
" \"x\": 2,"+
" \"y\": 70"+
" }"+
" },"+
" \"2345263767467354\": {"+
" \"random_double\": \"0.1235667456456\","+
" \"magic\": \"23\","+
" \"health\": 1,"+
" \"price\": 9,"+
" \"point\": {"+
" \"x\": 0,"+
" \"y\": 70"+
" }"+
" }"+
" }"+
"}";
Gson g = new Gson();
Outer object = g.fromJson(json, Outer.class);
System.out.print(object);
}
}
This is the result:
Outer [this_number=1, that_number=12, some_string=String!, list_of_objects={342356676784653234535345=Inner [random_double=0.1235667456456, magic=29, health=1, price=7, point=Point [x=2, y=70]], 2345263767467354=Inner [random_double=0.1235667456456, magic=23, health=1, price=9, point=Point [x=0, y=70]]}]