Gson to json add map dynamically to object - java

I have an object that represents an event that I would like to serialize into json, using gson or another library if that works easier.
I want to add the following type of field to the json:
private Map<String, String> additionalProperties;
And also in another use case:
private Map<String, Object> additionalProperties;
But if I add additionalProperties to the Event object, and build Gson in the normal way:
Gson gson = BUILDER.create();
String json = gson.toJson(event);
It will appear like so:
additional_properties: {"value1":1,"value2":"abc"}
I would simply like to append to the event object in the following form:
{"value1":1,"value2":"abc"}
Here is an example output- the additional properties added are the object 'z' and the object 'advertiser':
{"organisationid":"2345612ß","projectid":"12345678",
"place":{"placeId":"2345","last_place":"123-3"},
"advertiser":{"advertiserId":"2345a","code":"a123-3"},
"user":{"isY":false,"isHere":false,"isBuyer":false},
"x":{"identifier":"SHDG-28CHD"},
"z":{"identifier":"abcSHDG-28CHD"},
"event_type":"x_depart"}
Here is what it currently looks like:
{"organisationid":"2345612ß","projectid":"12345678",
"place":{"placeId":"2345","last_place":"123-3"},
additionalproperty: {"advertiser":{"advertiserId":"2345a","code":"a123-3"},
"user":{"isY":false,"isHere":false,"isBuyer":false},
"x":{"identifier":"SHDG-28CHD"},
additionalproperty: {"z":{"identifier":"abcSHDG-28CHD"}},
"event_type":"x_depart"}

The best way to solve this would be with a framework for adding properties dynamically, but without this, you could add the additional properties to your Event object (include #JsonIgnore
so that it is not part of the final json), create a new JSONObject from the additional properties and merge it to the event JSONObject before serializing to json. This way the additional properties are added dynamically to the resulting Event output.
In the Event class:
#JsonIgnore
private Map<String, Object> additionalProperties;
public Map<String, Object> getAdditionalProperties() {
return this.additionalProperties;
}
public void setAdditionalProperties(Map<String, Object> additionalProperties) {
this.additionalProperties = additionalProperties;
}
A function to merge two JSONObjects:
public static JSONObject mergeObjects(JSONObject source, JSONObject target) throws JSONException {
for (String key: JSONObject.getNames(source)) {
Object value = source.get(key);
if (!target.has(key)) {
// new value for "key":
target.put(key, value);
} else {
// existing value for "key" - recursively deep merge:
if (value instanceof JSONObject) {
JSONObject valueJson = (JSONObject)value;
deepMerge(valueJson, target.getJSONObject(key));
} else {
target.put(key, value);
}
}
}
return target;
}
Putting the two objects together:
String jsonAdd = mapper.writeValueAsString(additional);
String jsonEvent = mapper.writeValueAsString(event);
JSONObject jsonAddObj = new JSONObject(jsonAdd);
JSONObject JsonEventObj = new JSONObject(jsonEvent);
JSONObject finalJson = Merge.deepMerge(jsonAddObj, JsonEventObj);

If your Event class is something like;
class Event {
private Map<String, Object> additionalProperties;
}
and you are calling;
String json = gson.toJson(event);
The ecpected and the valid output would be:
{"additionalProperties":{"value1":1,"value2":"abc"}}
If you want an output such as;
{"value1":1,"value2":"abc"}
You can call;
String json = gson.toJson(event.additionalProperties);
I would simply like to append to the event object in the following
form:
{"value1":1,"value2":"abc"}
That way it won't be a valid json variable if you append that value directly to a json object. You should better try if the json value you want is valid or not at http://jsonviewer.stack.hu/

Related

Cannot convert JSON response into Java object using JSON

I am trying to convert the http response body which is in JSON format into a Java object using the GSON library, but the object has all attributes equal to null after trying to do so.
My Object class:
public class User {
private String username;
private int shipCount;
private int structureCount;
private String joinedAt;
private int credits;
public User(String username, int shipCount, int structureCount, String joinedAt, int credits) {
this.username = username;
this.shipCount = shipCount;
this.structureCount = structureCount;
this.joinedAt = joinedAt;
this.credits = credits;
}
Plus getters and setters
My attempt at using GSON:
Gson gso = new Gson();
User userInf = gso.fromJson(response.body(), User.class);
System.out.println(userInf);
The response body is this:
{"user":{"username":":chrter","shipCount":0,"structureCount":0,"joinedAt":"2022-04-09T16:52:14.365Z","credits":0}}
Any help is greatly appreciated
The root object of your HTTP response is a JSON object that has only one field - "user". When GSON deserializes the response, it goes through all the fields of the root object and sets them to the corresponding fields of the class you have provided. So it will look for the field user in the class User and set it to the JSON's data.
Since class User has no field user, GSON doesn't fill that field. In fact it doesn't fill out any other fields, because there are no other fields in the root object.
To get around this you need to deserialize not the whole response, but only the user field of the root object. There are two ways of doing that.
Deserialize the root object, and then deserialize the field user of that object into the User class.
You can find an example of how to do it in #Saheed's answer. However you should note that it translates JSON string into java objects and then translates java objects back. It might cost you additional time if you're doing it in performance-sensitive area of your program.
Create another class which you will deserialize into, which would have the field user. It looks like this:
class Response {
public User user;
};
class User {
// ...
};
And then you deserialize like this:
Gson gso = new Gson();
// CHANGE: Deserialize the response and get the user field
Response response = gso.fromJson(response.body(),Response.class);
User userInf = response.user;
System.out.println(userInf);
Try something like this:
public static Map<String, Object> Converter(String str){
Map<String, Object> map = new Gson().fromJson(str, new TypeToken<HashMap<String, Object>>() {}.getType());
return map;
}
Map<String, Object> apiResponse = Converter(response.body().toString());
Map<String, Object> username = Converter(apiResponse.get("user").toString());
System.out.println(username);
Tweak it a bit to suit your need
Try this.
Map<?, ?> map = gson.fromJson(response.body(), Map.class);
for (Map.Entry<?, ?> entry : map.entrySet()) {
System.out.println(entry.getKey() + "=" + entry.getValue());
}

Fetching attributes of a POJO in generic way

I am fetching attributes of an object as List, where AvsAttribute is as follows:
class AvsAttribute {
String attrName,
String value
}
I am fetching the attribute values for any entity as follows, as I did not want to use reflection, that's why did like that:
#Override
public List<AvsAttribute> getAttributes(List<String> attributeNameList, final #NonNull T entity)
throws JsonProcessingException, JSONException {
List<AvsAttribute> attributeList = new ArrayList<>();
objectMapper.setSerializationInclusion(Include.NON_NULL);
String jsonString = objectMapper.writeValueAsString(entity);
JSONObject jsonObject = new JSONObject(jsonString);
Iterator<String> keysItr = jsonObject.keys();
while (keysItr.hasNext()) {
String key = keysItr.next();
String value = jsonObject.get(key).toString().trim();
attributeList.add(AvsAttribute.builder().name(key).value(value).build());
}
if (CollectionUtils.isNotEmpty(attributeNameList)) {
return attributeList.stream().filter(item -> attributeNameList.contains(item.getName()))
.collect(Collectors.toList());
}
return attributeList;
}
But I want to make AvsAttribute generic like below:
class AvsAttribute<T> {
String attrName,
T value
}
But I cannot figure out what change should I do to the above getAttributes() function so that it works with above generic class.
I think it already works with a generic type parameter. You need to setup the .value(String value) function in AvsAttribute builder to safely parse a given string into type T.
Then, you can rest assured that the following line will correctly fetch the generic attribute value:
AvsAttribute.builder().name(key).value(value).build()
Does that make sense?
Anyway, Good luck with your code.
Use wildcard ? in attributeList declaration and return type.

Java Jackson: parse three level JSON dynamic object structure

I'm trying to use the Java Jackson ObjectMapper to parse a three level JSON object stucture with dynamic keys. I tried the following:
public class AssetsPushManifest {
private Map<String, List<Asset>> manifest = new HashMap<>();
public void addPushManifest(Resource manifestResource) throws Exception {
ObjectMapper mapper = new ObjectMapper();
Map<String, Map<String, Asset>> manifestData = mapper.readValue(manifestResource.getInputStream(), new TypeReference<Map<String, Map<String, Asset>>>() {});
for (String requestedPathName : manifestData.keySet()) {
if (!this.manifest.containsKey(requestedPathName)) {
this.manifest.put(requestedPathName, new LinkedList());
}
List<Asset> requestedPath = this.manifest.get(requestedPathName);
for (String servePath : manifestData.get(requestedPathName).keySet()) {
Asset asset = manifestData.get(requestedPathName).get(servePath);
asset.path = servePath;
requestedPath.add(asset);
}
}
...
}
public class Asset {
public String path;
public String type;
public Integer weight;
}
}
To parse this:
{
"theme/test-theme/index.html": {
"theme/test-theme/somestyling.css": {
"type": "document",
"weight": 1
}
}
}
But it won't work, why oh why? Is it too many levels? (still Java beginner here)
The end goal is to parse the several JSON structures like above into a structure like Map> so any other ways of doing this would also be fine.
I would solve this in a different way: parse the json into a map: if you give Jackson a map as type reference, it will deserialize the JSON into multi-level map:
`Map<String, Object> manifestData = mapper.readValue(manifestResource.getInputStream(), Map.class);`
Now that the json parsing hurdle is behind us, it is easier to construct an instance of an Asset by querying the map. I would do it by adding a constructor to the Asset class:
public Asset(Map<String, Object> manifestData) {
Map<String, Object> assetData = (Map<String, Object>)manifestData.values().iterator().next(); // get the single value of the map
this.path = assetData.keySet().iterator().next();
this.type = assetData.get("type");
this.weight = assetData.get("weight");
}

JsonDeserializer is not working with gson

I have a list(list contains the requiredFields and this list can get the dynamic data from text file) and I have api response jsonData.
Now, I need to extract the data from api(jsonData) response, only the required fields(what list contained fields). All this need to be done using gson serializer
public class EDSJsonSerializer implements JsonDeserializer {
final list<String>; // list can be populated by reading data from text
file
//ex: list<Strin> is : [ab,bc]
#Override
public JsonElement deserialize(JsonElement jsonElement, Type type, JsonDeserializationContext jsonDeserializationContext) throws JsonParseException {
jsonElement => {"ab":"234234","bc":"wrwerewe","ww":"345fsd","456":"dfgdfg"}
final Map map = new HahMap();
map should contain only 2 elements {"ab":"234234","bc":"wrwerewe"}
map can be populated with list above given as keys and values from json passed
}
}
final String json = ""; // json is the api response string
final GsonBuilder builder = new GsonBuilder();
builder.registerTypeAdapter(Map.class, new EDSJsonSerializer());
final Gson gson = builder.create();
final String map = gson.toJson(json);
it is not working as expected and not throwing any error/exception.
Please help me on this
Thanks,
Syamala.
Firstly 456 is not a valid Java class field name. It might be one reason of your problems even you might never use it from Json.
Secondly you migh better use ExclusionStrategy to decide which fields to de- & serialize.
In your case something like:
#RequiredArgsConstructor
public class ExcludeUnlistedFields implements ExclusionStrategy {
#NonNull
private Set<String> fieldsToInclude;
#Override
public boolean shouldSkipField(FieldAttributes f) {
// if you need to restrict to specific a class/classes
// add the checks here also
return ! fieldsToInclude.contains(f.getName());
}
#Override
public boolean shouldSkipClass(Class<?> clazz) {
return false;
}
}
and use it like:
Set<String> fieldsToInclude = new HashSet<>(Arrays.asList("ab", "bc"));
ExclusionStrategy es = new ExcludeUnlistedFields(fieldsToInclude);
Gson gson = new GsonBuilder().setPrettyPrinting()
.addDeserializationExclusionStrategy(es).create();

Jackson serialization with including null values for Map

I've the following POJOs that I want to serialize using Jackson 2.9.x
static class MyData {
public Map<String, MyInnerData> members;
}
static class MyInnerData {
public Object parameters;
public boolean isPresent;
}
Now if I populate the data with the following sample code as shown:
Map<String, Object> nodeMap = new LinkedHashMap<>();
nodeMap.put("key-one", "val1");
nodeMap.put("key-null", null);
nodeMap.put("key-two", "val2");
Map<String, Object> child1 = new LinkedHashMap<>();
child1.put("c1", "v1");child1.put("c2", null);
nodeMap.put("list", child1);
MyInnerData innerData1 = new MyInnerData();
innerData1.parameters = nodeMap;
innerData1.isPresent = true;
MyData myData = new MyData();
myData.members = new LinkedHashMap<>();
myData.members.put("data1", innerData1);
// serialize the data
ObjectMapper mapper = new ObjectMapper().setSerializationInclusion(JsonInclude.Include.NON_NULL);
String actual = mapper.writeValueAsString(myData);
System.out.println(actual);
This ends up printing
{"members":{"data1":{"parameters":{"key-one":"val1","key-two":"val2","list":{"c1":"v1"}},"isPresent":true}}}
Desired Output (same code gives correct output with Jackson 2.8.x )
{"members":{"data1":{"parameters":{"key-one":"val1","key-null":null,"key-two":"val2","list":{"c1":"v1","c2":null}},"isPresent":true}}}
If I remove the setSerializationInclusion(JsonInclude.Include.NON_NULL) on the mapper, I get the required output but the existing configuration for the ObjectMapper cant be changed. I can make changes to the POJO however.
What would be the least change to get the desired output?
You need to configure your object like this: mapper.setDefaultPropertyInclusion(JsonInclude.Include.ALWAYS);

Categories

Resources