Jackson serialization with including null values for Map - java

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

Related

How to recursively parse json value in ObjectMapper.readValue?

I'm trying to parse json strings inside a json string into an Object using Jackson ObjectMapper. But I can't find a clean way to do this.
I have a SQL table storing Lists as json strings like "[1,2,3]". And I read all columns out into a Map then tries to use objectMapper.convertValue to make into a Java Object.
So here's a quick snippet to recreate the problem. Do note I don't control how the Map is generated in the actual code.
#Data
public class Main {
private List<Integer> bar;
public static void main(String[] args) throws Exception{
ObjectMapper objectMapper = new ObjectMapper();
Map<String, Object> objectMap = new HashMap<String, Object>();
objectMap.put("bar", "[1,2,3]");
// Main foo = objectMapper.convertValue(objectMap, Main.class)
String json = objectMapper.writeValueAsString(objectMap);
Main foo = objectMapper.readValue(json, Main.class);
System.out.println(foo.getBar());
}
}
But this is not right. Instead of parsing the string, ObjectMapper tries to convert String to List directly and failed. I would expect foo.getBar() returns a List with 3 elements, but the code already failed at converting stage.
You should make "[1,2,3]" as String[] so:
"[1,2,3]".replaceAll("\\[", "").replaceAll("\\]", "").replaceAll("\\s", "").split(",")
Something like:
public static void main(String[] args) throws Exception {
ObjectMapper objectMapper = new ObjectMapper();
Map<String, Object> objectMap = new HashMap<String, Object>(){{
put("bar", "[1,2,3]".replaceAll("\\[", "")
.replaceAll("]", "")
.replaceAll("\\s", "").split(","));
}};
String json = objectMapper.writeValueAsString(objectMap);
Main foo = objectMapper.readValue(json, Main.class);
System.out.println(foo.getBar());
}
you just need to replace the "[1,2,3]" with what ever you getting it from.
Note: Not sure about the application logic

java convert map into a pojo when attribute names are different

Is it possible to convert map into a pojo when attribute names are different?
I am extracting raw input into a map to have the following data. Data can vary based on message type. For example:
for Message type = STANDARD
Map<String, Double> data = new HashMap<>();
data.set('TEMP', 18.33);
data.set('BTNUM', 123);
for Message type = NON_STANDARD
Map<String, Double> data = new HashMap<>();
data.set('GPSLAT', 12.33);
data.set('GPSLON', 42.33);
For each message type I have a Java model class
#Data
public class StandardMessage {
private String longitude;
private String latitude;
}
#Data
public class NonStandardMessage {
private String temperature;
private String btNumber;
}
Currenly I am mapping data to POJO class manually like below
StandardMessage sm = new StandardMessage();
sm.setLongitude(data.get('GPSLON'));
NonStandardMessage nsm = new NonStandardMessage();
nsm.setTemperature(data.get('TEMP'));
Is it possible to make above mapping generic? i.e setting object property without knowing name?
In Typescript we can achieve this easily by defining configuration like:
objectPropertyMapping = new Map();
objectPropertyMapping.set('GPSLAT', 'latitude');
objectPropertyMapping.set('GPSLON', 'longitude');
standardMessage = {};
data.forEach((value: boolean, key: string) => {
standardMessage[ObjectPropertyMapping.get(key)] = data[key];
});
https://stackblitz.com/edit/angular-zjn1kc?file=src%2Fapp%2Fapp.component.ts
I know Java is a statically-typed language, just wondering is there a way to achieve this like typescript or we have to map manually all the time?
We use jackson-databind. It uses annotations for configuration.
Here are some example:
The entity class:
class MessageRequest {
#JsonProperty("A")
private String title;
#JsonProperty("B")
private String body;
... getters and setters ...
}
The main method:
public static void main(String[] args) {
ObjectMapper objectMapper = new ObjectMapper();
Map<String, Object> source = new HashMap<>();
source.put("A", "This is the title");
source.put("B", "Here is the body");
MessageRequest req = objectMapper.convertValue(source, MessageRequest.class);
System.out.println(req.getTitle());
System.out.println(req.getBody());
}

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

Gson to json add map dynamically to object

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/

how to get custom made object from ObjectMapper in java

I am doing the following:
IronRunId Id = new IronRunId("RunObject", "Runid1", 4);
ObjectMapper MAPPER = new ObjectMapper();
HashMap<String, Object> map = new HashMap<String, Object>();
map.put("RunId", Id);
String json = MAPPER.writeValueAsString(map);
ObjectMapper mapper = new ObjectMapper();
Map<String, Object> map1 = mapper.readValue(json, new TypeReference<Map<String, Object>>() {});
IronRunId runId = (IronRunId) (map1.get("RunId"));
But this gives me an error: Cannot cast java.util.LinkedHashMap to IronRunId
Why is the object returned by map.get() of type linkedhashmap?
On the contrary, if I do:
List<Object> myList = new ArrayList<Object>();
myList.add("Jonh");
myList.add("Jack");
map.put("list", myList);
Then the object returned by map.get() after doing mapper.readValue is of type ArrayList.
Why the difference? Inserting default types into the map returns the correct object. But inserting custom made object in the map does not.
Does anyone know how to do this?
Map<String, Object> map1 = mapper.readValue(json, new TypeReference<Map<String, Object>>() {});
basically translated to, return me a Map with keys of type String and values of type Object. So, Jackson gave you keys of type String and values of type Object. Jackson doesn't know about your custom object, thats why it gave you its own native bound for Object which is a Map, specifically, a LinkedHashMap, and thus the reason why your are getting a LinkedHashMap when doing a get to the returned Map
So change it to :
Map<String, IronRunId> map1 = mapper.readValue(json, new TypeReference<Map<String, IronRunId>>() {});
Also, it is a good practice to declare an Object of its interface type than its concrete type. So instead of
HashMap<String, Object> map = new HashMap<String, Object>();
make it
Map<String, Object> map = new HashMap<String, Object>();
Edit
As a response to your added questions, you can create a wrapper object that will handle all your objects. Something like this.
class Wrapper{
private IronRunId ironRunId;
private long time;
private Map<String, String> aspects;
private String anotherString;
public long getTime() {
return time;
}
public void setTime(long time) {
this.time = time;
}
public Map<String, String> getAspects() {
return aspects;
}
public void setAspects(Map<String, String> aspects) {
this.aspects = aspects;
}
public String getAnotherString() {
return anotherString;
}
public void setAnotherString(String anotherString) {
this.anotherString = anotherString;
}
public IronRunId getIronRunId() {
return ironRunId;
}
public void setIronRunId(IronRunId ironRunId) {
this.ironRunId = ironRunId;
}
}
You can then store different objects in this class.
Revised version
public static void main(String[] args) throws JsonGenerationException, JsonMappingException, IOException{
IronRunId Id = new IronRunId("RunObject", "Runid1", 4);
Map<String, String> aspects = new HashMap<String, String>();
aspects.put("aspectskey1", "aspectsValue1");
aspects.put("aspectskey2", "aspectsValue2");
aspects.put("aspectskey3", "aspectsValue3");
String anotherString = "anotherString";
long time = 1L;
Wrapper objectWrapper = new Wrapper();
ObjectMapper objectMapper = new ObjectMapper();
objectWrapper.setIronRunId(Id);
objectWrapper.setTime(time);
objectWrapper.setAnotherString(anotherString);
objectWrapper.setAspects(aspects);
Map<String, Wrapper> map = new HashMap<String, Wrapper>();
map.put("theWrapper", objectWrapper);
String json = objectMapper.writeValueAsString(map);
Map<String, Wrapper> map1 = objectMapper.readValue(json, new TypeReference<Map<String, Wrapper>>() {});
Wrapper wrapper = map1.get("theWrapper");
System.out.println("run id : " + wrapper.getIronRunId().toString());
System.out.println("time : " + wrapper.getTime());
System.out.println("aspects : " + wrapper.getAspects().toString());
System.out.println("anotherString : " + wrapper.getAnotherString());
}
new TypeReference<Map<String, Object>> is too generic. It is equivalent Map or "untyped" Map mentioned in Data Binding With Generics. The only way for you to deserialize different datatypes in a map or collection is to use TypeFactory.parametricType

Categories

Resources