Apply a mask on a JSON to keep only mandatory data - java

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"
+ "}";
}
}

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

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;
}
}
}

Jackson Array to Pojo Not Working Only on Android

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

Globally ignore class in Jackson

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}

GSON: Deserializing Json with random class names

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]]}]

Categories

Resources