Variable Names In JSON List Followed By Object Details - java

I've got a curious JSON to work with that I need to be able to map to a Java object. The environment I'm working in doesn't have access to Guava's Multimap (if that even is a solution), and I've considered being able to extend some sort of base class with a variable class name (if that is even possible), but I'm out of my depth on this one.
What sort of Java object allows lists of objects with unique, varying references to the same object class?
Here's a sample of the JSON I'm working with, I've confirmed it's a valid JSON via JSON formatter:
{
"apple1":{
"orchard":"green groves orchard",
"zipcode": 34567,
"speciesId": 12345,
"applePickedNumber": 6437896,
"knownProducts":{
"green grove apple butter":{
"productId":"ABC123456789",
"manufacturer":"red barn cannery",
"shipper":"hermes shipping"
}
}
},
"apple2":{
"orchard":"fair pastures orchard",
"zipcode": 34567,
"internalSpeciesId": 10001,
"speciesId": 23456,
"applePickedNumber": 145,
"knownProducts":{}
}
}
Thanks in advance for your help!

I think that the answer is probably Object[] and Map<String,Object>. But if you are going to map your JSON to types like that, then you should probably abandon the idea of mapping, and just use the JSONObject and JSONArray types.
Mappings only really work if the JSON conforms to a fixed "schema" with fixed attribute names. You can't map JSON to POJO classes if the attribute names keep changing.

Example when using Jackson. Define other attributes in Object class
ObjectMapper objectMapper = new ObjectMapper();
TypeReference<HashMap<String, Object>> typeRef
= new TypeReference<>() {
};
HashMap<String, Object> result = objectMapper.readValue(json, typeRef);

Related

Java POJO attributes mapping

I have a use case where I receive some attributes in the request like this,
"filters": [
{
"field": "fName",
"value": "Tom"
},
{
"field": "LName",
"value": "Hanks"
}
]
I don't have a model defined for this. I just receive these attributes in the request and fire a query on elastic search using these attributes. My records in elastic search have the same attribute names.
Now, I have to support a legacy application where attribute's names are completely different.
E.g.: fName becomes firstName and lName becomes lastName.
Problem: Need to accept old attribute names in the request, convert them to new ones so that it matches my elastic search records. Fetch the data with new attribute names and convert back to old ones before sending out the response from the application.
NOTE: I don't have POJO's defined for these records.
How can this be achieved effectively? I was thinking of using Orika mapper but not sure how that will work without defining classes first.
What prevents you from writing a transformer from request JSON to your normalized JSON?
The normal flow I can think of is:
Request JSON -> POJO -> POJO with normalized value -> Normalized JSON
So your POJO looks like:
public class Filter {
List<FieldFilter> filters;
public static class FieldFilter {
private String field;
private String value;
}
}
Now you will have a transformation map like:
Map<String, String> fieldNameMapping = new HashMap<>();
fieldNameMapping.put("fName", "firstName");
fieldNameMapping.put("firstName", "firstName");
// The process of populating this map can be done either by a static initializer, or config/properties reader
Then you transform your POJO:
Filter filterRequest;
List<FieldFilters> normlizedFilters =
filterReq.getFilters().stream()
.map(f -> new FieldFilter(fieldNameMapping.get(f.getField()), f.getValue())
.collect(toList());
Then convert the Filter class to your normalized JSON.
We have a similar scenario and we are using apache JOLT.If you want to try some samples, you can refer jolt-demo-online-utility
Use a JSON to JSON-transformer instead. Good answers regarding this can be found here: JSON to JSON transformer and here : XSLT equivalent for JSON
In the end you do not require an intermediate object type here. You even said, that you do not have such a type yet and inventing it, just to transform it, doesn't really make sense.

Design Java class for Jackson when JSON key has no one name but different values

How to model in Java (for Jackson library) following json file where the key is the file name so it has no (constant) name
{
"core/core-rwd/src/scss/_colors.scss": [
{
"line": 1,
"column": 13,
},
I would like to have something like
class MySet {
???? files;
}
class File {
int line;
int column;
}
What should I replace ??? with to make this compatible with Jackson?
Assuming that name is dynamic, you won't be able to map it to a POJO type field.
The solution is to deserialize the JSON to a Map<String, Something[]>. The Map's values can still be some known type if they do map to a POJO type.
Alternatively, you can use Jackson's ObjectNode, a Map-like data structure with methods that make sense in a JSON context.

How to deserialize this json

This is the json response returned by MediaWiki API. I want to create a class to be able to deserialize it to it use Jackson library. The problem is that this json contains a key which is different from each request (here is 290).
{
"query-continue": {
"revisions": {
"rvcontinue": 633308090
}
},
"query": {
"pages": {
"290": {
"pageid": 290,
"ns": 0,
"title": "A",
"revisions": [
{
"user": "Mr. Guye",
"timestamp": "2014-12-07T17:45:55Z",
"comment": "comment",
"contentformat": "text/x-wiki",
"contentmodel": "wikitext",
"*": "content"
}
]
}
}
}
}
How could create a class (or configure the mapper) to be able to deserialize this json?
You can deserialize JSON to multiple formats using Jackson. One way that you mentioned is to convert the JSON to a POJO which may be difficult when the keys are dynamic. Another approach is to deserialize the JSON to the Jackson Tree Model which is called JsonNode. The following illustrates how you can parse the provided JSON to a JsonNode and then retrieve the various attributes.
final ObjectMapper mapper = new ObjectMapper();
// Parse the JSON, deserialize to the Tree Model
final JsonNode jsonNode = mapper.readTree(jsonString);
// Get hold of the "query -> pages" node.
final JsonNode pages = jsonNode.path("query").path("pages");
// Iterate the pages
for (final JsonNode page : pages) {
// Work with the page object here...
System.out.println(page.get("pageid")); // -> 290
}
The JsonNode object is very flexible and contains various convenience functions for accessing the data. As shown in the example above the path() and get() methods are two ways of accessing the data. If you use get() the property MUST exist, if you use path the property MAY exist. Furthermore, there are multiple ways of iterating the sub-elements and the loop shown above is one way.
Take a look at the Jackson docs for more info.
The short answer is you can't, at least not in the current format with that abominable asterisk being present. Therefore, we will have to employ a bit of hackery here to get the job done, and I warn you upfront, it's not going to be pretty.
Firstly, copy that response, then go to http://www.jsonschema2pojo.org/ and paste it into the JSON textbox. After pasting it, change the asterisk to something more civilized, like "content". Select JSON (default is JSON Schema) for the Source Type, input your package and root class name respectively, and click JAR to generate the package with all the POJO's that map to this JSON. You could also click "Preview" and copy paste the code into your source files -- it's really up to you.
Now that we have a valid version of this JSON structure, we use Jackson to read it in. If your JSON String is called jsonResponse and the corresponding POJO class is MediaWiki, then you convert it with Jackson like this:
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
MediaWiki mw = objectMapper.readValue(profileJson, MediaWiki.class);
The key here is the FAIL_ON_UNKNOWN_PROPERTIES being set to false, which means it will ignore that asterisk, and create everything else for you.
Now, to actually grab whatever value was present for that asterisk and store it into our "content" attribute (or whatever else you wanted replace the asterisk with), you are going to have to parse this sucker out client-side and pass it as a separate input parameter, and to do that, you will have to yank it out by calling something like this:
var content = query.pages.290.revisions["*"];
This content parameter is passed and stored it into your POJO's content attribute.
I know it's a lot of work, and if anyone else has a more elegant solution, please share. As I said, mine was not going to be pretty. :-)
This looks like key value pair.
You can use map in order to deserialize key value pairs:
public class Query {
private Map<Integer, Page> pages;
public Map<Integer, Page> getPages() {
return pages;
}
public void setPages(Map<Integer, Page> pages) {
this.pages = pages;
}
}
Jackson handles such deserialization by default.

Constructing POJO out of JSON string with dynamic fields using Gson

I'm consuming a web service in my application that will return a list of ID's associated with a name. An example would look like this:
{
"6502": "News",
"6503": "Sports",
"6505": "Opinion",
"6501": "Arts",
"6506": "The Statement"
}
How would I construct a POJO for Gson to deserialize to when all of the fields are dynamic?
How about deserializing into a map?
Gson gson = new Gson();
Type mapType = new TypeToken<Map<String, String>>() {}.getType();
String json = "{'6502':'News','6503':'Sports','6505':'Opinion','6501':'Arts','6506':'The Statement'}";
Map<String, String> map = gson.fromJson(json, mapType);
Using a map sounds reasonable for me (as Java is statically typed). Even if this could work (maybe using JavaCompiler) - accessing the object would not probably be much different from accessing a map.
I don't know Gson that well but I suspect that's not possible. You'd have to know which fields are possible beforehand, although the fields might not be in the Json and thus be null.
You might be able to create classes at runtime by parsing the Json string, but I don't know whether that would be worth the hassle.
If everything is dynamic your best bet would be to deserialize the Json string to maps of strings or arrays etc. like other Json libraries do (I don't know whether Gson can do this as well, but the classes you need are commonly called JSONObject and JSONArray).
So your Json string above would then result in a Map<String, String>.

Flattening of json to a map with Jackson

I am currently using the Jackson library to unmarshal json to pojos, using annotations.
The json tree that I would like to unmarshal is as follows:
{
"key1":"value1",
"key2":"value2",
"key3":{
"key31":{
"key311":"value311",
"key312":"value312",
"key313":"value313"
},
"key32":"value32"
},
"key4":"value4",
"key5":"value5",
"key6":{
"key61":"value61"
}
}
I don't know the json structure in advance and would like to completely flatten it to a Map which content would be equivalent to:
Map<String, Object> outputMap = new HashMap<String, Object>();
outputMap.put("key1", "value1");
outputMap.put("key2", "value2");
outputMap.put("key311", "value311");
outputMap.put("key312", "value312");
outputMap.put("key313", "value313");
outputMap.put("key32", "value32");
outputMap.put("key4", "value4");
outputMap.put("key5", "value5");
outputMap.put("key61", "value61");
(note that keys "key3", "key31" and "key6" should be ignored)
Using the annotation #JsonAnySetter, I can create a function to populate the map with all top level atoms, but the method will also catch the node having children (when the value is a Map).
From that point, I can of course write myself the simple recursion over the children, but I would like this part to be handled automatically (in an elegant way) through the use of a facility from the library (annotation, configuration, etc.), and not have to write the recursion by myself.
Note: we assume there is not name clashing in the different level keys.
There is no annotation-based mechanism to do this that I know of, since #JsonUnwrapped which might be applicable is only usable with POJOs, not Maps.
I guess you could file an enhancement request to ask #JsonUnwrapped to be extended to also handle Map case, although it seems only appicable for serialization (not sure how one could deserialize back).

Categories