Parse JSON with JsonArray and HashMap - java

I need to parse the following JSON and add values from it into three different Java objects. I was thinking to form other 3 Jsons in order to do this. I have some issues with parsing, as the JSON is a little bit complicated. The JSON is below:
{
"totalCount": 1,
"results": [
{
"teleCommunications": [
{
"areaCode": "100",
"telephoneNumber": "300-2444",
"internationalAreaCode": "",
"communicationType": 1
},
{
"areaCode": "100",
"telephoneNumber": "200-2555",
"internationalAreaCode": "",
"communicationType": 5
}
],
"delegate": {
"id": 0,
"range": 0,
},
"name": "Andrew",
"composedKey": {
"id": 615,
"range": 50,
},
"isBranchAddress": false,
"emailAddresses": [
{
"emailAddressType": 9,
"emailAddress": "andrew.brown#gmail.com"
}
],
"name": "Brown",
"zipCodeCity": "65760 Leipzig",
"salutation": "Mr.",
"openingDate": "2019-09-20",
"streetHouseNumber": "Offenbach. 37",
"modificationTimestamp": "2018-01-27"
}
]
}
I need to get separately the values from name, zipCodeCity, salutation, openingDate, streetHouseNumber in a JSON ( or any other way) , emailAddresses in a different JSON and the other data in another one.
I tried with this piece of code:
ObjectMapper mapper = new ObjectMapper();
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
HashMap<String, Object> result = new ObjectMapper().readValue(response, HashMap.class);
Object object = result.get("results");
List<HashMap<String, String>> jsonValues = (List<HashMap<String, String>>)object;
for(String key : jsonValues.get(0).keySet()){
System.out.println("name value " + jsonValues.get(0).get("name"));
System.out.println("zip code City " + jsonValues.get(0).get("zipCodeCity"));
}
The problem is that I would not go for such a hardcoded way...it is not very suitable.
Does anyone know a better approach for storing the values I need and parsing the Json more optimally?
Thank you

Look for this link, there's a demo application specially for you: https://github.com/nalmelune/jackson-demo-58591850 (look at JacksonTest)
First of all, you'll need data-classes for this (or so called dto), representing structure. You can use online generators for that (google "json to java dto online") or do it yourself. For example, here's root object (see github link for more):
public class Root {
private int totalCount;
private List<Results> results;
public void setTotalCount(int totalCount) {
this.totalCount = totalCount;
}
public int getTotalCount() {
return this.totalCount;
}
public void setResults(List<Results> results) {
this.results = results;
}
public List<Results> getResults() {
return this.results;
}
}
Then configure your ObjectMapper:
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new JavaTimeModule());
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
And use it (don't create new mapper, as it is in your example):
Root result = mapper.readValue(response, Root.class);
Finally, you can access it as usual Plain Old Java Objects:
for (Results resultItem : result.getResults()) {
System.out.println(resultItem.getSalutation());
System.out.println(resultItem.getName());
System.out.println(resultItem.getZipCodeCity());
System.out.println(resultItem.getOpeningDate());
System.out.println(resultItem.getStreetHouseNumber());
}
To make it work be sure validate your json (there were invalid commas after "range", and "name" comes twice and so on, it fixed on github). And be sure to include jsr310 in classpath by adding com.fasterxml.jackson.datatype:jackson-datatype-jsr310 module. You will need it for LocalDate and LocalDateTime objects.

You may create java class something like below
#Data
class MyCustomClass{
int totalCount;
List<TeleCommunicationDetail> results;
// other props
}
Next you can add attributes to TeleCommunicationDetail and finally you can use MyCustomClass reaonseInJavaObject = objectMapper.readValue(myJson,MyCustomClass.class);
Refer to this question for details.

Your approach is correct, as you simply read your JSON object as Map<String, Object> which always will work (as long as JSON Object is valid). And then you retrieve your values that you expect to be there. Another approach is to create a Class that maps to your original JSON and parse JSON with ObjectMapper into your class. Then you can retrieve your data with your class setters and getters. In this case, you don't need to use key Strings like "results" etc but you have to ensure that your JSON is not only valid JSON but always conforms to your class. Pick your option...

Related

Using Jackson to extract information from Property names

I currently receive the following JSON body
{
"productId": "90000011",
"offerId": "String",
"format": "String",
"sellerId": "String",
"sellerName": "String",
"shippingPrice[zone=BE,method=STD]": 0.0,
"deliveryTimeEarliestDays[zone=BE,method=STD]": 1,
"deliveryTimeLatestDays[zone=BE,method=STD]": 1,
"shippingPrice[zone=NL,method=STD]": 0.0,
"deliveryTimeEarliestDays[zone=NL,method=STD]": 1,
"deliveryTimeLatestDays[zone=NL,method=STD]": 1
}
As you can see, I have similar properties that differ by zone and method enclosed in square brackets. I don't want to change the code every time a new zone and/or method is introduced. I'm looking for a more dynamic way you deserialize this via Jackson.
Is there a way to automatically deserialize all properties starting with shippingPrice, deliveryTimeEarliestDays and deliveryTimeLatestDays into the following format?
{
"productId": "90000011",
"offerId": "String",
"format": "String",
"sellerId": "String",
"sellerName": "String",
"deliveryModes":[
{
"method":"STD"
"zone":"BE",
"shippingPrice":0.0,
"deliveryTimeEarliestDays":1,
"deliveryTimeLatestDays":1
},{
"method":"STD"
"zone":"NL",
"shippingPrice":0.0,
"deliveryTimeEarliestDays":1,
"deliveryTimeLatestDays":1
}]
}
My first idea was to use the #JsonAnySetter annotation and put everything in a Map but that still leaves me with manual parsing of the field name.
My Second Idea was to build a custom deserializer where I loop over all attributes and filter out all the ones that start with shippingPrice, deliveryTimeEarliestDays and deliveryTimeLatestDays and map them to the described format above.
In order to achieve the required result, you need to implement deserialization logic yourself, it can't be done only by sprinkling a couple of data binding annotations.
That's how it can be done.
Assume here's a POJO that corresponds to your input JSON (to avoid boilerplate code, I'll use Lombok annotations):
#Getter
#Setter
public static class MyPojo {
private String productId;
private String offerId;
private String format;
private String sellerId;
private String sellerName;
#JsonIgnore // we don't want to expose this field to Jackson as is
private Map<DeliveryZoneMethod, DeliveryMode> deliveryModes = new HashMap<>();
#JsonAnySetter
public void setDeliveryModes(String property, String value) {
DeliveryZoneMethod zoneMethod = DeliveryZoneMethod.parse(property);
DeliveryMode mode = deliveryModes.computeIfAbsent(zoneMethod, DeliveryMode::new);
String name = property.substring(0, property.indexOf('['));
switch (name) {
case "shippingPrice" -> mode.setShippingPrice(new BigDecimal(value));
case "deliveryTimeEarliestDays" -> mode.setDeliveryTimeEarliestDays(Integer.parseInt(value));
case "deliveryTimeLatestDays" -> mode.setDeliveryTimeLatestDays(Integer.parseInt(value));
}
}
public Collection<DeliveryMode> getModes() {
return deliveryModes.values();
}
}
Properties productId, offerId, format, sellerId, sellerName would be parsed by Jackson in a regular way.
And all other properties formatted like "shippingPrice[zone=BE,method=STD]" would be handled by the method annotated with #JsonAnySetter.
To facilitate extracting and storing information from such properties I've defined a couple of auxiliary classes:
DeliveryZoneMethod which contains information about a zone and delivery method as its name suggests (the purpose of this class is to serve as Key in the map deliveryModes).
DeliveryMode which is meant to contain all the need information that correspond to a particular zone and method of delivery.
For conciseness, DeliveryZoneMethod can be implemented as a Java 16 record:
public record DeliveryZoneMethod(String method, String zone) {
public static Pattern ZONE_METHOD = Pattern.compile(".+zone=(\\p{Alpha}+).*method=(\\p{Alpha}+)");
public static DeliveryZoneMethod parse(String str) {
// "shippingPrice[zone=BE,method=STD]" - assuming the given string has always the same format
Matcher m = ZONE_METHOD.matcher(str);
if (!m.find()) throw new IllegalArgumentException("Unable to parse: " + str);
return new DeliveryZoneMethod(m.group(1), m.group(2));
}
}
And here's how DeliveryMode might look like:
#Getter
#Setter
public static class DeliveryMode {
private String method;
private String zone;
private BigDecimal shippingPrice;
private int deliveryTimeEarliestDays;
private int deliveryTimeLatestDays;
public DeliveryMode(DeliveryZoneMethod zoneMethod) {
this.method = zoneMethod.method();
this.zone = zoneMethod.zone();
}
}
Usage example:
public static void main(String[] args) throws JsonProcessingException {
String json = """
{
"productId": "90000011",
"offerId": "String",
"format": "String",
"sellerId": "String",
"sellerName": "String",
"shippingPrice[zone=BE,method=STD]": 0.0,
"deliveryTimeEarliestDays[zone=BE,method=STD]": 1,
"deliveryTimeLatestDays[zone=BE,method=STD]": 1,
"shippingPrice[zone=NL,method=STD]": 0.0,
"deliveryTimeEarliestDays[zone=NL,method=STD]": 1,
"deliveryTimeLatestDays[zone=NL,method=STD]": 1
}
""";
ObjectMapper mapper = new ObjectMapper();
MyPojo myPojo = mapper.readValue(json, MyPojo.class);
String serializedJson = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(myPojo);
System.out.println(serializedJson);
}
Output:
{
"productId" : "90000011",
"offerId" : "String",
"format" : "String",
"sellerId" : "String",
"sellerName" : "String",
"modes" : [ {
"method" : "BE",
"zone" : "STD",
"shippingPrice" : 0.0,
"deliveryTimeEarliestDays" : 1,
"deliveryTimeLatestDays" : 1
}, {
"method" : "NL",
"zone" : "STD",
"shippingPrice" : 0.0,
"deliveryTimeEarliestDays" : 1,
"deliveryTimeLatestDays" : 1
} ]
}
I would go with your first idea to deserialize your JSON into a map. And yes you will still need to analyze the map keys. It is easy to deserialize Json into a map with Json Jackson, but there is an Open source library called MgntUtils that provides class JsonUtils which is a thin wrapper over Json-Jackson library. using it you can very simply deserialize your Json into a Map (or any other class). Your code would look like this:
try {
Map<String, Object> map = JsonUtils.readObjectFromJsonString(jsonStr, Map.class);
System.out.println(map);
} catch (IOException e) {
...
}
Here is Javadoc for JsonUtils. The library can be obtained as maven artifact or on Github (with source code and Javadoc).
Disclaimer: This library is written and maintained by me

Handling empty JSON

I need to send following JSON in API BODY POST request:
{
"name": "",
"type": "TEMP",
"shared": false,
"search": {
},
"order": [
]
}
In my MainBody.java, declared
private String name;
private String type;
private boolean shared;
private JSON search;
private Object order;
and defined getters and setters.
In Payload.java,
MainBody mb = new MainBody();
mb.setName("");
mb.setType("TEMP");
mb.setShared(false);
mb.setSearch(null);
mb.setOrder(new ArrayList<>());
ObjectMapper om = new ObjectMapper();
String myData = om.writerWithDefaultPrettyPrinter().writeValueAsString(mb);
System.out.println(myData);
results
{
"name" : "",
"type" : "TEMP",
"shared" : false,
"search" : null,
"order" : [ ]
}
Please assist with how search as { } can be achieved as per expected JSON instead of null.
TIA.
Instead of setting search to null, you need to set it to an empty object. I'm not sure which JSON library you are using, but there should be an object constructor like new JsonObject(). Depending on what the allowed values for search are, you may also want to consider representing it in your class as Map<String, String> or something like that.
I would try something like this:
mb.setSearch(new JSON());
This way you create empty object and there should be only {}. It also depends on which JSON library do you use.
Issue resolved after using JSONMapper.

JSON To ArrayList<CustomObject> With GSON While Ignoring Certain Fields

I am looking to create an ArrayList of custom objects from a JSON feed using GSON. My current approach works fine for a single JSON object holding an array, but now I need to parse a more complex JSON object. The first JSON feed looked like this:
{"data":
{"item_id": "1", "element": "element1"}
{"item_id": "2", "element": "element2"}
{"item_id": "3", "element": "element3"}
...
}
My method for extracting each item was using a simple custom object and parsing the JSON into an ArrayList of these objects.
InputStreamReader input = new InputStreamReader(connection.getInputStream());
Type listType = new TypeToken<Map<String, ArrayList<CustomObject>>>(){}.getType();
Gson gson = new GsonBuilder().create();
Map<String, ArrayList<CustomObject>> tree = gson.fromJson(input, listType);
ArrayList<CustomObject> = tree.get("data");
The current JSON object looks like this:
{"rate_limit": 1, "api_version": "1.2", "generated_on": "2015-11-05T19:34:06+00:00", "data": [
{"collection": [
{"item_id": "1", "time": "2015-11-05T14:40:55-05:00"},
{"item_id": "2", "time": "2015-11-05T14:49:09-05:00"},
{"item_id": "3", "time": "2015-11-05T14:51:55-05:00"}
], "collection_id": "1"},
{"collection": [
{"item_id": "1", "time": "2015-11-05T14:52:01-05:00"},
{"item_id": "2", "time": "2015-11-05T14:49:09-05:00"},
{"item_id": "3", "time": "2015-11-05T14:51:55-05:00"}
], "collection_id": "2"
]}
And I am having trouble parsing it because of the mixed types of data, some are numbers, strings and lastly arrays. I have a custom object built that takes an array of another custom object. This is the collection object:
public class CustomCollection {
private String collection_id;
private ArrayList<CustomItem> collection_items = new ArrayList<>();
public CustomCollection() {
this(null, null);
}
public CustomCollection(String id, ArrayList<CustomItem> items) {
collection_id = id;
collection_items = items;
}
public String getId() {
return collection_id;
}
public ArrayList<CustomItem> getItems() {
return collection_items;
}
}
And this is the item object:
public class CustomItem {
private String item_id;
private String item_element;
public CustomItem() {
this(null, null);
}
public CustomItem(String id, String element) {
item_id = id;
item_element = element;
}
public String getId() {
return item_id;
}
public String getElement() {
return item_element;
}
}
I do not really care about obtaining the other elements (i.e. "rate_limit", "api_version", "generated_on"), I just want to pass the "data" element into an ArrayList of objects. But when I try something similar to my original method, the parser stops a the first object because it receives a number instead of an array. Resulting in an IllegalStateException: Expected BEGIN_ARRAY but was NUMBER at line 1 column 17 path $.. I would like to know how I can either get the parser to ignore the other elements, or how I can get each element separately using GSON.
EDIT: The proposed solution to my problem, found in Ignore Fields When Parsing JSON to Object, technically does solve my issue. But this seems like a lengthy process that is unnecessary in my case. I have found a much simpler solution to my problem, posted in my answer below. I am also unsure if this method would work well for the aforementioned question, considering there does not seem to be a way to get a JsonObject from a JsonArray by key name in GSON.
The solution I have found is to parse the data into a com.google.gson.JsonObject, then into a JsonArray by key name. I can then use this JsonArray as a parameter in Gson.fromJson() to extract the data into my ArrayList of custom objects.
InputStreamReader input = new InputStreamReader(connection.getInputStream());
JsonArray data = new JsonParser().parse(input).getAsJsonObject().getAsJsonArray("data");
Type listType = new TypeToken<ArrayList<CustomCollection>>(){}.getType();
ArrayList<CustomCollection> collection = new Gson().fromJson(data, listType);
input.close();
This method ignores all other JSON fields, only obtaining the one specified. It also takes the array of objects from within the "data" object and puts them into the ArrayList of custom objects within CustomCollection.

Reading value of nested key in JSON with Java (Jackson)

I'm a new Java programmer coming from a background in Python. I have weather data that's being collected/returned as a JSON with nested keys in it, and I don't understand how pull the values out in this situation. I'm sure this question has been asked before, but I swear I've Googled a great deal and I can't seem to find an answer. Right now I'm using json-simple, but I tried switching to Jackson and still couldn't figure out how to do this. Since Jackson/Gson seem to be the most used libraries, I'd would love to see an example using one of those libraries. Below is a sample of the data, followed by the code I've written so far.
{
"response": {
"features": {
"history": 1
}
},
"history": {
"date": {
"pretty": "April 13, 2010",
"year": "2010",
"mon": "04",
"mday": "13",
"hour": "12",
"min": "00",
"tzname": "America/Los_Angeles"
},
...
}
}
Main function
public class Tester {
public static void main(String args[]) throws MalformedURLException, IOException, ParseException {
WundergroundAPI wu = new WundergroundAPI("*******60fedd095");
JSONObject json = wu.historical("San_Francisco", "CA", "20100413");
System.out.println(json.toString());
System.out.println();
//This only returns 1 level. Further .get() calls throw an exception
System.out.println(json.get("history"));
}
}
The function 'historical' calls another function that returns a JSONObject
public static JSONObject readJsonFromUrl(URL url) throws MalformedURLException, IOException, ParseException {
InputStream inputStream = url.openStream();
try {
JSONParser parser = new JSONParser();
BufferedReader buffReader = new BufferedReader(new InputStreamReader(inputStream, Charset.forName("UTF-8")));
String jsonText = readAll(buffReader);
JSONObject json = (JSONObject) parser.parse(jsonText);
return json;
} finally {
inputStream.close();
}
}
With Jackson's tree model (JsonNode), you have both "literal" accessor methods ('get'), which returns null for missing value, and "safe" accessors ('path'), which allow you to traverse "missing" nodes. So, for example:
JsonNode root = mapper.readTree(inputSource);
int h = root.path("response").path("history").getValueAsInt();
which would return the value at given path, or, if path is missing, 0 (default value)
But more conveniently, you can just use JSON pointer expression:
int h = root.at("/response/history").getValueAsInt();
There are other ways too, and often it is more convenient to actually model your structure as Plain Old Java Object (POJO).
Your content could fit something like:
public class Wrapper {
public Response response;
}
public class Response {
public Map<String,Integer> features; // or maybe Map<String,Object>
public List<HistoryItem> history;
}
public class HistoryItem {
public MyDate date; // or just Map<String,String>
// ... and so forth
}
and if so, you would traverse resulting objects just like any Java objects.
Use Jsonpath
Integer h = JsonPath.parse(json).read("$.response.repository.history", Integer.class);
Check out Jackson's ObjectMapper. You can create a class to model your JSON then use ObjectMapper's readValue method to 'deserialize' your JSON String into an instance of your model class. And vice-versa.
Try jpath API. It's xpath equivalent for JSON Data. You can read data by providing the jpath which will traverse the JSON data and return the requested value.
This Java class is the implementation as well as it has example codes on how to call the APIs.
https://github.com/satyapaul/jpath/blob/master/JSONDataReader.java
Readme -
https://github.com/satyapaul/jpath/blob/master/README.md
Example:
JSON Data:
{
"data": [{
"id": "13652355666_10154605514815667",
"uid": "442637379090660",
"userName": "fanffair",
"userFullName": "fanffair",
"userAction": "recommends",
"pageid": "usatoday",
"fanPageName": "USA TODAY",
"description": "A missing Indonesian man was found inside a massive python on the island of Sulawesi, according to local authorities and news reports. ",
"catid": "NewsAndMedia",
"type": "link",
"name": "Indonesian man swallowed whole by python",
"picture": "https:\/\/external.xx.fbcdn.net\/safe_image.php?d=AQBQf3loH5-XP6hH&w=130&h=130&url=https%3A%2F%2Fwww.gannett-cdn.com%2F-mm-%2F1bb682d12cfc4d1c1423ac6202f4a4e2205298e7%2Fc%3D0-5-1821-1034%26r%3Dx633%26c%3D1200x630%2Flocal%2F-%2Fmedia%2F2017%2F03%2F29%2FUSATODAY%2FUSATODAY%2F636263764866290525-Screen-Shot-2017-03-29-at-9.27.47-AM.jpg&cfs=1&_nc_hash=AQDssV84Gt83dH2A",
"full_picture": "https:\/\/external.xx.fbcdn.net\/safe_image.php?d=AQBQf3loH5-XP6hH&w=130&h=130&url=https%3A%2F%2Fwww.gannett-cdn.com%2F-mm-%2F1bb682d12cfc4d1c1423ac6202f4a4e2205298e7%2Fc%3D0-5-1821-1034%26r%3Dx633%26c%3D1200x630%2Flocal%2F-%2Fmedia%2F2017%2F03%2F29%2FUSATODAY%2FUSATODAY%2F636263764866290525-Screen-Shot-2017-03-29-at-9.27.47-AM.jpg&cfs=1&_nc_hash=AQDssV84Gt83dH2A",
"message": "Akbar Salubiro was reported missing after he failed to return from harvesting palm oil.",
"link": "http:\/\/www.usatoday.com\/story\/news\/nation-now\/2017\/03\/29\/missing-indonesian-man-swallowed-whole-reticulated-python\/99771300\/",
"source": "",
"likes": {
"summary": {
"total_count": "500"
}
},
"comments": {
"summary": {
"total_count": "61"
}
},
"shares": {
"count": "4"
}
}]
}
Code snippet:
String jPath = "/data[Array][1]/likes[Object]/summary[Object]/total_count[String]";
String value = JSONDataReader.getStringValue(jPath, jsonData);

Injecting additional fields in serialization

I have objects which I serialize using Jackson. e.g.
class A {
int id;
String value
}
is serialized to:
{
"id": 1,
"value": "test"
}
Now I want an additional field in the serialized version:
{
"id": 1,
"value": "test"
"__errors__": [
{ field: "id", "message": "already in use" },
{ field: "value", "message": "field to long" },
]
}
This field is composed by a list of class Error:
class Error {
String field;
String message;
}
This should be done while serializing and work without adding a method / property to the class A.
So is there a way to inject additional fields into jackson mapper? Best would be a generic method which would work for all classes passed to the mapper without additional code.
(Background: I want to use this to return validation errors to AngularJS within the normal result. Because putting both in a container would complicate the code on the client side.)
Thanks in advance!
EDIT:
I imagine the final usage could look something like this:
ObjectMapper mapper = ...;
PrintWriter out = ...;
// Normally the parsed user input
A a = new A( 5, "test" );
// Normally generated by validation of user input
List<Error> errors = Arrays.asList( new Error( "id", "already in use" ) );
// No write it as combined json
mapper.writeValue( out, a, error );
// ... or ...
customWrite( out, a, error );
EDIT2:
Currently I'm looking at two possible solutions:
Write a custom Serializer. Seems like a clean solution but needs to do all the stuff the default serializer does. But is a complex solution because I first have to figure out how custom serialization and extending the default works.
Just use String replacement and concatenation. Write both objects seperately and remove staring/closing curly braces and append one on another. Quick, but very dirty. (I really don't like this but it could work.)
You can easily do that using ObjectMapper.convertValue method. See below example:
A a = new A(101, "test");
List<Error> errors = Arrays.asList(new Error("id", "already in use"), new Error("value",
"field to long"));
// Create Jackson objects
ObjectMapper jsonMapper = new ObjectMapper();
MapType mapType = jsonMapper.getTypeFactory().constructMapType(LinkedHashMap.class,
String.class, Object.class);
// Create map
LinkedHashMap<String, Object> map = jsonMapper.convertValue(a, mapType);
map.put("__errors__", errors);
// Serialize map
String json = jsonMapper.writerWithDefaultPrettyPrinter().writeValueAsString(map);
System.out.println(json);
Above example prints:
{
"id" : 101,
"value" : "test",
"__errors__" : [ {
"field" : "id",
"message" : "already in use"
}, {
"field" : "value",
"message" : "field to long"
} ]
}

Categories

Resources