Java Jackson: parse three level JSON dynamic object structure - java

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

Related

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

Jackson deserialize extra fields as Map

I'm looking to deserialize any unknown fields in a JSON object as entries in a Map which is a member of a POJO.
For example, here is the JSON:
{
"knownField" : 5,
"unknownField1" : "926f7c2f-1ae2-426b-9f36-4ba042334b68",
"unknownField2" : "ed51e59d-a551-4cdc-be69-7d337162b691"
}
Here is the POJO:
class MyObject {
int knownField;
Map<String, UUID> unknownFields;
// getters/setters whatever
}
Is there a way to configure this with Jackson? If not, is there an effective way to write a StdDeserializer to do it (assume the values in unknownFields can be a more complex but well known consistent type)?
There is a feature and an annotation exactly fitting this purpose.
I tested and it works with UUIDs like in your example:
class MyUUIDClass {
public int knownField;
Map<String, UUID> unknownFields = new HashMap<>();
// Capture all other fields that Jackson do not match other members
#JsonAnyGetter
public Map<String, UUID> otherFields() {
return unknownFields;
}
#JsonAnySetter
public void setOtherField(String name, UUID value) {
unknownFields.put(name, value);
}
}
And it would work like this:
MyUUIDClass deserialized = objectMapper.readValue("{" +
"\"knownField\": 1," +
"\"foo\": \"9cfc64e0-9fed-492e-a7a1-ed2350debd95\"" +
"}", MyUUIDClass.class);
Also more common types like Strings work:
class MyClass {
public int knownField;
Map<String, String> unknownFields = new HashMap<>();
// Capture all other fields that Jackson do not match other members
#JsonAnyGetter
public Map<String, String> otherFields() {
return unknownFields;
}
#JsonAnySetter
public void setOtherField(String name, String value) {
unknownFields.put(name, value);
}
}
(I found this feature in this blog post first).

com.google.gson.internal.LinkedTreeMap cannot be cast to object [duplicate]

I have JSON file looks like
{
"SUBS_UID" : {
"featureSetName" : "SIEMENSGSMTELEPHONY MULTISIM",
"featureName" : "MULTISIMIMSI",
"featureKey" : [{
"key" : "SCKEY",
"valueType" : 0,
"value" : "0"
}
]
},
}
So the key is a String "SUBS_ID" and the value is a model called FeatureDetails which contains attributes "featureSetName,featureName,...".
So i read from the JSON file using google.json lib like this,
HashMap<String, FeatureDetails> featuresFromJson = new Gson().fromJson(JSONFeatureSet, HashMap.class);
then I'm trying to loop over this HashMap getting the value and cast it to my FeatureDetails model,
for (Map.Entry entry : featuresFromJson.entrySet()) {
featureDetails = (FeatureDetails) entry.getValue();
}
and here is my FeatureDetails Model,
public class FeatureDetails {
private String featureSetName;
private String featureName;
private ArrayList<FeatureKey> featureKey;
private String groupKey;
private String groupValue;
public FeatureDetails() {
featureKey = new ArrayList<FeatureKey>();
}
public ArrayList<FeatureKey> getFeatureKey() {
return featureKey;
}
public void setFeatureKey(ArrayList<FeatureKey> featureKey) {
this.featureKey = featureKey;
}
public String getGroupKey() {
return groupKey;
}
public void setGroupKey(String groupKey) {
this.groupKey = groupKey;
}
public String getGroupValue() {
return groupValue;
}
public void setGroupValue(String groupValue) {
this.groupValue = groupValue;
}
public String getFeatureName() {
return featureName;
}
public void setFeatureName(String featureName) {
this.featureName = featureName;
}
public String getFeatureSetName() {
return featureSetName;
}
public void setFeatureSetName(String featureSetName) {
this.featureSetName = featureSetName;
}
}
but i got an exception "com.google.gson.internal.LinkedHashTreeMap cannot be cast to com.asset.vsv.models.FeatureDetail".
try this:
HashMap<String, FeatureDetails> featuresFromJson = new Gson().fromJson(JSONFeatureSet, new TypeToken<Map<String, FeatureDetails>>() {}.getType());
and when you going through your hash map do this:
for (Map.Entry<String, FeatureDetails> entry : featuresFromJson.entrySet()) {
FeatureDetails featureDetails = entry.getValue();
}
The reason you're seeing this is because you're telling GSON to deserialize the JSON structure using the structure of a HashMap in the line
... = new Gson().fromJson(JSONFeatureSet, HashMap.class);
^^
Right here
As a result, GSON has no idea that the sub objects in the JSON are anything other than simple key-value pairs, even though the structure may match the structure of your FeatureDetails object.
One solution is to create a model which wraps your FeatureDetails object, which will act as the root of the entire structure. This object might look something like this:
public class FeatureDetailsRoot{
private FeatureDetails SUBS_UID; // poor naming, but must match the key in your JSON
}
And finally, you'd pass that model's class:
= new Gson().fromJson(JSONFeatureSet, FeatureDetailsRoot.class)
Update
In answer to your question in the comment regarding the ability to add / have multiple FeatureDetails objects, the problem presently is that your JSON does not reflect that kind of structure. Meaning, the "SUBS_UID" key points to a single object, not an array objects. If you would like to have this ability, then your json will need to be altered so that it shows an array of objects, like this:
{
"SUBS_UID" : [{
"featureSetName" : "Feature set name #1",
...attributes for feature #1
},
{
"featureSetName" : "Feature set name #2",
...attributes for feature #2
},
...other features
]
}
And then you can simply alter the root class so that it contains a list of FeatureDetails objects, like so:
public class FeatureDetailsRoot{
private List<FeatureDetails> SUBS_UID;
}
Let me know if that makes sense (or whether I've misunderstood you)
(objectName as Map<String, Any>).get("fieldName")
the code is in Kotlin:
use val type = object : TypeToken<HashMap<String, FoodLogEntry>>() {}.type
Gson().fromJson(dataStr, type)
instead of val type = object : TypeToken<Map<String, FoodLogEntry>>() {}.type
Gson().fromJson(dataStr, type)
note: HashMap instead of Map

How do I map a JsonNode to an ArrayList of Object

I have an input JSON Node as following
"headers" : {
"name1" : "value1",
"name2" : "value2"
}
My Java class looks as follows :
public class OptionalHeader{
private String name;
private String value;
public OptionalHeader() {
super();
}
public OptionalHeader(String name, String value) {
this.name = name;
this.value = value;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}
I want to create an arrayList of OptionalHeader like below from the JsonNode.
List<OptionalHeader> optionalHeaders = new ArrayList<OptionalHeader>();
Any help would be appreciated. Thanks :)
Thanks for all the replies :)
I required the JsonNode coming via REST request containing key->value pairs to be converted into an ArrayList<OptionalHeader>, see the OptionalHeader class in question.
finally did it using following:
(getting it as JsonNode in my POJO) coming via REST request. I used below in my setter.
public void setOptHeaders(JsonNode optHeaders) throws JsonParseException, JsonMappingException, IOException {
this.optHeaders = optHeaders;
List<OptionalHeader> allHeaders = new ArrayList<OptionalHeader>();
Iterator<Entry<String, JsonNode>> nodeIterator = optHeaders.fields();
while (nodeIterator.hasNext()) {
Map.Entry<String, JsonNode> entry = (Map.Entry<String, JsonNode>) nodeIterator.next();
OptionalHeader header = new OptionalHeader(entry.getKey(),entry.getValue().asText());
allHeaders.add(header);
}
optionalEmailHeaders.addAll(allHeaders);
}
then following in the getter to convert it back.
public JsonNode getOptHeaders() {
final ObjectMapper mapper = new ObjectMapper();
final Map<String, String> map = new HashMap<String, String>();
for (final OptionalHeader data: optionalEmailHeaders)
map.put(data.getName(), data.getValue());
optHeaders = mapper.valueToTree(map);
return optHeaders;
}
Jackson mapper is there which can map Java Objects from/to JSON objects.
You can get the Jackson API from here.
Try saving your JSON string into a file eg. 'headers.json'.
Jackson provides a ObjectMapper class and you can use its readValue( File file, Class class) method to get the Java object from JSON like :-
ObjectMapper mapper = new ObjectMapper();
OptionalHeader optHeader= mapper.readValue(new File("c:\\headers.json"), OptionalHeader .class);
You can use the same link for getting the docs & read more about it.

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/

Categories

Resources