Access deep objects in a map using a key like "item1.item1A" - java

I have several maps whose keys are strings and its values Objects (Map<String, Object>). Those objects could be other maps, lists or literal values (similar to a JSON representation in memory). Example:
myMap = {
"users": [
{
"name": "Foo",
"surname": "Bar"
},
{
"name": "John",
"surname": "Doe"
}
]
}
I don't want to reinvent the wheel, so I was wondering if there is a Java library that would allow me to do something like this:
String secondUserSurname = (String) myMap.get("users[1].surname");
Thanks
EDIT: To clarify, I need the expression "users[1].surname" to be a string. I would have this data structure in memory, and the expression in a .properties file. The properties file will tell which object to access within the map. That way I can rapidly change the object to access changing the properies file, without changing the Java code that would require a deployment which is costly.

If you have content available as String or bytes then you can use any json library to deserialise it into a POJO and get the fields. Below is an example with Jackson:
class UserObject {
private List<User> users;
public List<User> getUsers() {
return users;
}
public void setUsers(List<User> users) {
this.users = users;
}
}
class User {
private String name;
private String surname;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSurname() {
return surname;
}
public void setSurname(String surname) {
this.surname = surname;
}
}
Driver code:
String content = "{" +
" \"users\": [" +
" {" +
" \"name\": \"Foo\"," +
" \"surname\": \"Bar\"" +
" }," +
" {" +
" \"name\": \"John\"," +
" \"surname\": \"Doe\"" +
" }" +
" ]" +
"}";
ObjectMapper objectMapper = new ObjectMapper();
UserObject userObject = objectMapper.readValue(content, UserObject.class);
System.out.println(userObject.getUsers().get(0).getSurname());

The library that allows me to access these objects is JXPath, as Gyro Gearless pointed in the comments, credit to him.
https://commons.apache.org/proper/commons-jxpath/

Related

Response with lowercase letters in serialization and deserialization Jackson

The response of a WS is a json with first capitalize letters. I'm trying to encapsulate the response in a new MyResponse obj having lowercase first letters.
I'm using Jackson.
At first I have my models:
public class Telephone {
private String country;
private String prefix;
//getters and setters
}
public class Position {
private String x;
private String y;
//getters and setters
}
public class Root {
#JsonProperty("Telephone")
private List<Telephone> telephone;
#JsonProperty("Position")
private List<Position> position;
//getters and setters
}
public class MyResponse {
private final Root root;
private final String now;
public MyResponse(Root root, String now) {
this.root = root;
this.now = now;
}
//getters
}
As you can see above, I used #JsonProperty in my Root class because I want to map my response using a first lowercase letter.
Now I have my RestController:
#Controller
public class RestController {
#GetMapping("/my-controller")
ResponseEntity<String> myController() {
//Simulating the request to my ws to get my json string
String jsonString = "{\n" +
" \"Telephone\":[\n" +
" {\n" +
" \"country\":\"ES\",\n" +
" \"prefix\":\"+34\"\n" +
" },\n" +
" {\n" +
" \"country\":\"FR\",\n" +
" \"prefix\":\"+33\"\n" +
" },\n" +
" {\n" +
" \"country\":\"EN\",\n" +
" \"prefix\":\"+44\"\n" +
" }\n" +
" ],\n" +
" \"Position\":[\n" +
" {\n" +
" \"x\":\"123.23\",\n" +
" \"y\":\"98.93\"\n" +
" },\n" +
" {\n" +
" \"x\":\"250.99\",\n" +
" \"y\":\"43.89\"\n" +
" }\n" +
" ]\n" +
"}";
ObjectMapper om = new ObjectMapper();
Root root = null;
try {
root = om.readValue(jsonString, Root.class);
MyResponse myResponse = new MyResponse(root, LocalDateTime.now().toString());
String responseAsString = om.writeValueAsString(myResponse);
return new ResponseEntity<>(responseAsString, HttpStatus.OK);
} catch (JsonProcessingException e) {
e.printStackTrace();
}
return null;
}
As you can see in the snippet of code above, at the beginning I got the json string (in my real code calling the WS) and I deserialized it into a Java POJO using the readValue method:
root = om.readValue(jsonString, Root.class);
Then I created my MyResponse obj using the deserialized POJO:
MyResponse myResponse = new MyResponse(root, LocalDateTime.now().toString());
And at the end, I serialized myResponse obj to String using om.writeValueAsString and I returned it to my frontend:
String responseAsString = om.writeValueAsString(myResponse);
return new ResponseEntity<>(responseAsString, HttpStatus.OK);
Since MyResponse obj is serialized and deserialized both using my Root #JsonProperty (s)
I get:
{
"root":{
"Telephone":[
{
"country":"ES",
"prefix":"+34"
},
{
"country":"FR",
"prefix":"+33"
},
{
"country":"EN",
"prefix":"+44"
}
],
"Position":[
{
"x":"123.23",
"y":"98.93"
},
{
"x":"250.99",
"y":"43.89"
}
]
},
"now":"2021-06-24T11:18:04.077612"
}
That is not what I am trying to do: I have capitalize letters in my response.
How can I solve this problem? Should I use two different classes for serialization and deserialization?
You can specify your Jackson annotations on getters and setters as well to let them behave differently. Like this:
public class Root {
private List<Telephone> telephone;
#JsonProperty("Telephone")
private void setTelephone(String telephone) {
this.telephone = telephone;
}
#JsonProperty("telephone")
private String getTelephone() {
this.telephone = telephone;
}
}
I've tried what suggested by Felix. But it didn't work, I got the following error:
Conflicting/ambiguous property name definitions found multiple explicit names: but also implicit accessor
After a while I was able to solve my problem in this way:
public class Root {
private List<Telephone> telephone;
private List<Position> position;
#JsonCreator
public Root(#JsonProperty("Telephone") List<Telephone> telephon, #JsonProperty("Position") List<Position> position) {
this.telephon = telephon;
this.position = position;
}
//getters and setters
}
The annotation #JsonCreator is used in deserialization phase only.
When one deserializes Jackson uses the constructor annotated with #JsonCreator. In serialization phase Jackson uses the fields name to convert the obj into String.

Return part of JSON into object Spring boot

I have JSON structure that goes like this
{
"name":"John",
"age":27,
"company":{
"company_name":"ACME LLC",
"address": "1st Street",
"country": "US"
}
}
I know that if I want to map this to java object I need some kind of mapper and I have done that.
My question is: Is there a way to map only part of this json into object. I would like to map only company part of json into object. One more thing, I'm using java 11 with spring boot and I have access to Jackson's Object mapper.
Edit: Also if there is a way to navigate to this object it would be ok. For example $.company
I think the easiest way is to create two model classes. First with one field:
#Data
public class Model {
private Company company;
}
and second:
#Data
public class Company {
#JsonProperty("company_name")
private String companyName;
private String address;
private String country;
}
Then you can use Jackson mapper. You don't need to mention "map" and "age" fields in the Model class.
Ok, I have found an answer, thank you #Jakub on your answer it was a great help. Link to comment.
This is code example of answer based on my question:
public class Company {
private String name;
private String address;
private String country;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public String getCountry() {
return country;
}
public void setCountry(String country) {
this.country = country;
}
}
Test method/execution:
#Test
public void testJSON() throws JsonProcessingException {
JsonNode productNode = objectMapper.readTree("{\n" +
" \"name\":\"John\",\n" +
" \"age\":27,\n" +
" \"company\":{\n" +
" \"name\":\"ACME LLC\",\n" +
" \"address\": \"1st Street\",\n" +
" \"country\": \"US\"\n" +
" }\n" +
"}");
Company company = objectMapper.readValue(productNode.get("company").toString(), Company.class);
System.out.println(company.getAddress());
}
I would suggest using DSM library.
define your mapping in yaml file, you only want to company field. So your mapping file will be like below.
result:
type: object
path: /company
fields:
name: # read value from field in path
path: company_name
address: #field name is the same
country:
Java code to read file:
DSM dsm=new DSMBuilder(new File("path/to/mapping.yaml")).create();
Map<String,Object> result= ( Map<String,Object>)dsm.toObject(jsonData);
result is a map that contains information in company field in json.
If you want to directly get instance of the class by deserialization:
public class Company{
String name;
String address;
String country;
}
Java code to deserialize:
DSM dsm=new DSMBuilder(new File("path/to/mapping.yaml")).create();
Company result= dsm.toObject(jsonData,Company.class);

Creating POJO objects from JSON array String

I have a JSON array string like below.
"[
{
"id" : "123",
"name":"John Doe",
"Address":"53/A"
},
{
"id" : "1234",
"name":"John Doe1",
"Address":"53/AB"
}
]"
I have a POJO class which maps with the inner JSON objects like below.
class User {
String id;
String name;
String Address;
//Getters & Setters
}
I want to create POJO objects from the above String and create a User ArrayList. How can I achieve this ? (using a library like Gson is fine). Thank you.
Gson, since you mentioned it, offers a method (link) which is exactly what you're looking for.
Gson.fromJson​(java.lang.String json, java.lang.Class<T> classOfT)
If you want to deserialize a generic type (e.g. a list), docs advise you to use this method:
Gson.fromJson​(java.lang.String json, java.lang.reflect.Type typeOfT)
There is also a tutorial (link) which tackles a few use-cases close to what you want to do.
ArrayList<LinkedTreeMap> list = gson.fromJson(json, ArrayList.class);
List<User> users= list.stream()
.map(s -> gson.fromJson(gson.toJson(s), User.class))
.collect(Collectors.toList());
You can use Jackson ObjectMapper and its very simple. below is the example code.
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;
import java.util.List;
public class JsanParserExample {
public static void main(String[] args) throws IOException {
String json = "[\n" +
" {\n" +
" \"id\" : \"123\",\n" +
" \"name\":\"John Doe\",\n" +
" \"address\":\"53/A\"\n" +
" \n" +
" },\n" +
" {\n" +
" \"id\" : \"1234\",\n" +
" \"name\":\"John Doe1\",\n" +
" \"address\":\"53/AB\"\n" +
" \n" +
" }\n" +
" \n" +
"]";
ObjectMapper objectMapper = new ObjectMapper();
List<User> userList = objectMapper.readValue(json,new TypeReference<List<User>>() {});
userList.forEach(user -> System.out.println(user.getId()));
}
private static class User {
private String id;
private String name;
private String address;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
}
}
You can use Jackson. Simple example you can find here: https://www.baeldung.com/jackson-collection-array#to-array
Example:
ObjectMapper mapper = new ObjectMapper();
String jsonArray = "[{\"stringValue\":\"a\",\"intValue\":1,\"booleanValue\":true}, {\"stringValue\":\"bc\",\"intValue\":3,\"booleanValue\":false}]";
MyDto[] asArray = mapper.readValue(jsonArray, MyDto[].class);

How to ignore a specific field while parsing a JSON into map

I want to parse the below JSON into POJO. I am using jackson to parse the json.
{
"totalSize": 4,
"done": true,
"records": [
{
"attributes": {
"type": "oppor",
"url": "/service/oppor/456"
},
"AccountId": "123",
"Id": "456",
"ProposalID": "103"
}
]
}
In the above JSON, the fields "totalSize", "done", "records" and "attributes" are known fields. Whereas, "AccountId", "Id" and "ProposalID" are unknown fields. And in the above JSON, I don't need "attributes" to be part of my bean object.
And here is equivalent bean class for my JSON
public class Result {
private int totalSize;
private boolean done;
private List<Map<String, String>> records;
public int getTotalSize() {
return totalSize;
}
public void setTotalSize(int totalSize) {
this.totalSize = totalSize;
}
public boolean isDone() {
return done;
}
public void setDone(boolean done) {
this.done = done;
}
public List<Map<String,String>> getRecords() {
return records;
}
public void setRecords(List<Map<String, String>> records) {
this.records = records;
}
}
Hence there are unknown fields in the records element I just used List to get the results element in bean. Here in this Map, I don't want the field "attributes". How can I ignore this while parsing?
And below is the exception that I am getting as attributes is not a string element.
com.fasterxml.jackson.databind.JsonMappingException: Can not deserialize instance of java.lang.String out of START_OBJECT token
at [Source: [B#66fdec9; line: 1, column: 40] (through reference chain: com.sample.json.Result["records"])
at com.fasterxml.jackson.databind.JsonMappingException.from(JsonMappingException.java:164)
at com.fasterxml.jackson.databind.DeserializationContext.mappingException(DeserializationContext.java:691)
at com.fasterxml.jackson.databind.deser.std.StringDeserializer.deserialize(StringDeserializer.java:46)
at com.fasterxml.jackson.databind.deser.std.StringDeserializer.deserialize(StringDeserializer.java:11)
at com.fasterxml.jackson.databind.deser.std.MapDeserializer._readAndBindStringMap(MapDeserializer.java:430)
at com.fasterxml.jackson.databind.deser.std.MapDeserializer.deserialize(MapDeserializer.java:312)
at com.fasterxml.jackson.databind.deser.std.MapDeserializer.deserialize(MapDeserializer.java:26)
at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.deserialize(CollectionDeserializer.java:227)
at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.deserialize(CollectionDeserializer.java:204)
at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.deserialize(CollectionDeserializer.java:23)
UPDATE 2015/08/29:
As you have commented that
I achieved dynamic field support by parsing the JSON into map. Ignoring bad JSON element is what pending
I suggest that you should process original JSONObject to remove the "attributes" element from it.
Original JSONObject, for example:
{
"totalSize": 4,
"done": true,
"records": [
{
"attributes": {
"type": "oppor",
"url": "/service/oppor/456"
},
"AccountId": "123",
"Id": "456",
"ProposalID": "103"
}
]
}
After process, new JSONObject will be like the following:
{
"records": {
"AccountId": "123",
"Id": "456",
"ProposalID": "103"
},
"totalSize": 4,
"done": true
}
Use the code as the following:
JSONObject jsonObject;
try {
jsonObject = new JSONObject(jsonString1);
JSONArray jsonArray = new JSONArray(jsonObject.get("records").toString());
JSONObject jsonObject1 = jsonArray.getJSONObject(0);
jsonObject1.remove("attributes");
jsonObject.put("records", jsonObject1);
} catch (JSONException e) {
e.printStackTrace();
}
Then, use your own code that achieved dynamic field support by parsing the JSON into map.
END OF UPDATE 2015/08/29
I suggest that you use Gson and transient in this case
Like this
String jsonString1 = "{\n" +
" \"totalSize\": 4,\n" +
" \"done\": true,\n" +
" \"records\": [\n" +
" {\n" +
" \"attributes\": {\n" +
" \"type\": \"oppor\",\n" +
" \"url\": \"/service/oppor/456\"\n" +
" },\n" +
" \"AccountId\": \"123\",\n" +
" \"Id\": \"456\",\n" +
" \"ProposalID\": \"103\"\n" +
" }\n" +
" ]\n" +
"}";
Gson gson = new Gson();
Result result1 = gson.fromJson(jsonString1, Result.class);
Your classes, pay attention to transient:
public class Result {
private int totalSize;
private boolean done;
private List<Record> records;
}
public class Record {
private transient Map<String, String> attributes;
private int AccountId;
private int Id;
private int ProposalID;
}
You will get the result:
P/S: I tested in Android Studio :)
UPDATE:
String jsonString1 = "{\n" +
" \"totalSize\": 4,\n" +
" \"done\": true,\n" +
" \"records\": [\n" +
" {\n" +
" \"attributes\": {\n" +
" \"type\": \"oppor\",\n" +
" \"url\": \"/service/oppor/456\"\n" +
" },\n" +
" \"AccountId\": \"123\",\n" +
" \"Id\": \"456\",\n" +
" \"ProposalID\": \"103\"\n" +
" }\n" +
" ]\n" +
"}";
Gson gson = new Gson();
Object object = gson.fromJson(jsonString1, Object.class);
Map<String, String> stringMap = (Map<String, String>) object;
Result myResult = new Result();
Iterator entries = stringMap.entrySet().iterator();
while (entries.hasNext()) {
Map.Entry entry = (Map.Entry) entries.next();
String key = entry.getKey().toString();
String value = entry.getValue().toString();
switch (key) {
case "totalSize":
myResult.totalSize = (int) Double.parseDouble(entry.getValue().toString());
break;
case "done":
myResult.done = Boolean.valueOf(entry.getValue().toString());
break;
case "records":
try{
Object object1 = entry.getValue();
List<Object> objectList = (List<Object>) object1;
Map<String, Object> stringMap2 = (Map<String, Object>) objectList.get(0);
Map<String, String> recordMap = new HashMap<>();
Iterator entries2 = stringMap2.entrySet().iterator();
while (entries2.hasNext()) {
Map.Entry entry2 = (Map.Entry) entries2.next();
String key2 = entry2.getKey().toString();
String value2 = entry2.getValue().toString();
if (!"attributes".equals(key2)) {
recordMap.put(key2, value2);
}
entries2.remove();
}
myResult.records = recordMap;
} catch (Exception e) {
e.printStackTrace();
}
break;
}
entries.remove();
}
Classes:
public class Result {
private int totalSize;
private boolean done;
private Map<String, String> records;
}
Debug result:
1) Create a Record class object
2) Add #JsonIgnore Annotation on fields you won't
public class Result {
private int totalSize;
private boolean done;
private Record records;
[..]
}
public class Record {
#JsonIgnore
private Map<String, String> attributes;
private int accountID;
private int id;
private int approvalID;
[..]
}
Create a new POJO class for attributes,
public class Result {
private int totalSize;
private boolean done;
private List<Attributes> records;
// Your Getters & Setters
}
public class Attributes{
List<Map<String,String>> attributes;
// Add other variables if necessary like AccountId, etc.,
// Your Getters & Setters
}
I would suggest to use [Google gson API][1]'s #Expose annotation. (if that is allowed in your environment).
You can simply annotate the fields(with #Expose) which are required in your generated json file, and leave it other fields. And during generating json, use API method, excludeFieldsWithoutExposeAnnotation.
Sample example can be seen here.
Note : In your example, treat your Result as Main POJO, and records is another POJO which has attributes,accountId etc fields.
Then there is has-a relationship (Java composition) between them.
And after that, you can invoke Json to pojo conversion like below--
com.google.gson.Gson gson = new com.google.gson.GsonBuilder()
.excludeFieldsWithoutExposeAnnotation().create();
Result result= gson.fromJson(yourjsonString, Result.class);
If you have specific fields you don't want to map, u can use #JsonIgnore annotation over the field name
public class MyJsonObj{
#JsonProperty("name")
String fieldName
#JsonIgnore
String fieldNameIgnored;
}
If you want to ignore all the fields not mentioned in your POJO, you can use
#JsonIgnoreProperties annotation over class
#JsonIgnoreProperties(ignoreUnknown = true)
public class MyJsonObj{
strong text
}

How can I convert the following JSON string to Java Object by Gson?

I am having a problem to convert this json string to java object now. I have tried many ways, what I have got so far either errors like "Exception in thread "main" com.google.gson.JsonSyntaxException: com.google.gson.stream.MalformedJsonException: Expected value at line 1 column 44" I know the problem is from here \"errmsg\": in the string i made. Or errors like "Expected BEGIN_OBJECT but was BEGIN_ARRAY at line 1 column 49", if I delete the errmsg part of my string. While i was trying, i made it run several times, but all the values I got either null or 0's, and it didn't even go into class Products.
One additional question: how to write a website link to a string, I always got errors on this part, that's why I comment out those links in the string I made.
Thank you !
Here is my json:
{"data":{"results":4,"returned":1,"errmsg":""},"products":
[{"name":"aaa", "region":"USA > CA","price":"1,231.00","year":"2011",
"link":"http:\/\/www.aaa.com\/abc\/sss-ttt-2011\/",
"image":"http:\/\/pic.aaa.com\/media\/8\/aaa_12_abc.jpeg"},
{"name":"bbb","region":"USA > WA","price":"31.00","year":"2012",
"link":"http:\/\/www.bbb.com\/abc\/sss-ttt-2012\/",
"image":"http:\/\/pic.bbb.com\/media\/7\/bbb_12_abc.jpeg"}]}
This is what I have done:
import java.util.Arrays;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
public class product {
static String jsonInput = "{" +
"\"data\":" +
"{\"results\":4,\"returned\":2,\"errmsg\":}," +
"\"products\":" +
"[" +
"{\"name\":\"aaa\",\"region\":\"USA > CA\",\"price\":1,231.00,\"year\":2011,"+
//\"link\":\"http:\/\/www.aaa.com\/abc\/sss-ttt-2011\/\", +
//\"image\":\"http:\/\/pic.aaa.com\/media\/8\/aaa_12_abc.jpeg\" +
"{\"name\":\"bbb\",\"region\":\"USA > WA\",\"price\":31.00,\"year\":2012,"+
//\"link\":\"http:\/\/www.bbb.com\/abc\/sss-ttt-2012\/\", +
//\"image\":\"http:\/\/pic.bbb.com\/media\/8\/bbb_12_abc.jpeg\" +
"}"+
"]" +
"}";
public static void main(String[] args) {
GsonBuilder gsonBuilder = new GsonBuilder();
Gson gson = gsonBuilder.create();
Data data = gson.fromJson(jsonInput, Data.class);
System.out.println(data);
}
}
class Data {
private int results;
private int returned;
private String errmsg;
private Products products;
#Override
public String toString() {
return String.format(
"[data: results=%1$d, returned=%2$d, errmsg=%3$s, products=%4$s]",
results, returned, errmsg, products);
}
}
class Products {
private Products_info[] products_info;
#Override
public String toString() {
return String.format("[%1$s]", Arrays.toString(products_info));
}
}
class Products_info {
private String name;
private String region;
private double price;
private int year;
//private String link;
//private String image;
#Override
public String toString() {
return "[name=" + name + ", region=" + region + ", price=" + price +
", year=" + year +
//", link=" + link +
//", image=" + image +
"]";
}
}
Thank you so much!
Your String may be malformed, but as far as I can tell, the java objects you have defined doesn't translate into the JSON data at all.
This JSON:
{
"data": {
"results": 4,
"returned": 1,
"errmsg": ""
},
"products": [
{
"name": "aaa",
"region": "USA > CA",
"price": "1,231.00",
"year": "2011",
"link": "http://www.aaa.com/abc/sss-ttt-2011/",
"image": "http://pic.aaa.com/media/8/aaa_12_abc.jpeg"
},
{
"name": "bbb",
"region": "USA > WA",
"price": "31.00",
"year": "2012",
"link": "http://www.bbb.com/abc/sss-ttt-2012/",
"image": "http://pic.bbb.com/media/7/bbb_12_abc.jpeg"
}
]
}
Should translate into the following objects:
SomeObject {
public class Data {
private int results;
private int returned;
private String errmsg;
}
public class Product {
private String name;
private String regsion;
private String price;
private String year;
private String link;
private String image;
}
private Data data;
private List<Product> products;
}
Worth noting is that there are numerous online formatters that can escape strings properly so you don't have to do it by hand:
http://www.freeformatter.com/java-dotnet-escape.html#ad-output
http://www.htmlescape.net/javaescape_tool.html

Categories