Unable to model JSON with a random integer as KEY - java

I have a JSON response which looks like...
{
"profile": {
"userData": {
"338282892": [
{
"userIdentifier": "98shdub777hsjjsuj23",
"detail": "Test User DEV",
"type": "customer"
}
]
}
}
}
I have created a model, let's call it UserProfileModel.java. The model has properties using JSON to Java POJO converter, however when doing
UserProfileModel model = objectMapper.readValue(body, UserProfileModel.class);
I am getting below exception because the key user "338282892" because it can not be stored as variabale, for this case I tried to create map
Map<String, List<UserPropertiesModel>>
Here UserPropertiesModel is storing the userIdentifier, detail and type.
com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field "338282892"
I want to know if there is a way to deserialise this kind of JSON using object mapper such that I can do "object.getUserIdentifier()" or "object.getType()".

You can not model without having a valid "key" (which should be a string for mapping in a POJO class)
Alternatively, you can use a Map<String, Object> for traversing to the inner JSON blob.
Understand this,
Top layer is "profile" and anything as value to profile is value for map.
So you have a Map<"Profile" (a String), Object (whatever value profile has)> map1.
Then you do same for inner layer of "userData", so basically the object you stored in map1 is now again a Map<String, Object> map2 and same for deeper. layers.
This might not be the best approach but there is no way you can serialise this type of JSON using Mirconaut or Lombok or Spring.

Yes there is a way, with the use of a custom deserializer. Basically what you want to do is override the default behavior for deserializing a UserData object. Here's the deserializer definition:
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
public class UserDataDeserializer extends JsonDeserializer<UserData>{
#Override
public UserData deserialize(JsonParser p, DeserializationContext ctxt)
throws IOException, JsonProcessingException {
List<List<UserDataContent>> output = new ArrayList<>();
ObjectMapper mapper = new ObjectMapper();
JsonNode jsonNode = p.getCodec().readTree(p);
Iterator<JsonNode> iterator = jsonNode.elements();
while(iterator.hasNext()) {
JsonNode value = iterator.next();
System.out.println(value);
List<UserDataContent> obj = mapper.convertValue(value, new TypeReference<List<UserDataContent>>() {});
output.add(obj);
}
UserData returnVal = new UserData();
returnVal.setUserDataContent(output);
return returnVal;
}
}
And here's how you would use it:
ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
module.addDeserializer(UserData.class, new UserDataDeserializer());
mapper.registerModule(module);
UserProfileModel model = mapper.readValue(body, UserProfileModel.class);
Here is a github repository with a complete working example.

Related

How to convert a JsonNode instance to an actual pojo

At a certain point in my code, I have parse a JSON document, represented as a string, to a JsonNode, because I don't know yet the actual target pojo class type.
Now, some time later, I know the Class instance of the pojo and I want to convert this JsonNode to an actual pojo of that class (which is annotated with the proper #JsonProperty annotations). Can this be done? If so, how?
I am working with Jackson 2.10.x.
In this case you can use two methods:
treeToValue
convertValue
See below example:
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.File;
import java.util.StringJoiner;
public class JsonNodeConvertApp {
public static void main(String[] args) throws Exception {
File jsonFile = new File("./resource/test.json").getAbsoluteFile();
ObjectMapper mapper = new ObjectMapper();
JsonNode node = mapper.readTree(jsonFile);
System.out.println(mapper.treeToValue(node, Message.class));
System.out.println(mapper.convertValue(node, Message.class));
}
}
class Message {
private int id;
private String body;
// getters, setters, toString
}
Above code for JSON payload like below:
{
"id": 1,
"body": "message body"
}
prints:
Message[id=1, body='message body']
Message[id=1, body='message body']

Retrieve the values from JSON String by sending keys dynamically using jackson API?

I have a json string like
{
"profile":{
"personalInfo":{
"name":"Sample",
"address":"XXX",
"town":"YYY"
}
}
}
I want retrieve data by sending keys dynamically.
I can able to retrieve by giving key directly. But i don't want that.
ObjectMapper objectMapper = new ObjectMapper();
JsonNode jsonNode = objectMapper.readTree("aboveJsonString");
JsonNode subNode = jsonNode.path("profile");
But i have to give "profile" dynamically. I will get that parameter from api request. Suggest me the approach?
When you do not know name of a key in JSON Object you can iterate over all elements using elements which returns Iterator<JsonNode>. In your case you have two JSON Objects with one key-value pair and you can traverse it like below:
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.json.JsonMapper;
import java.io.File;
public class JsonPathApp {
public static void main(String[] args) throws Exception {
File jsonFile = new File("./resource/test.json").getAbsoluteFile();
ObjectMapper mapper = JsonMapper.builder()
.build();
JsonNode root = mapper.readTree(jsonFile);
JsonNode personalInfo = root.elements().next().elements().next();
System.out.println(personalInfo);
}
}
Above code prints:
{"name":"Sample","address":"XXX","town":"YYY"}

Java: Parsing JSON with unknown keys to Map

In Java, I need to consume JSON (example below), with a series of arbitrary keys, and produce Map<String, String>. I'd like to use a standard, long term supported JSON library for the parsing. My research, however, shows that these libraries are setup for deserialization to Java classes, where you know the fields in advance. I need just to build Maps.
It's actually one step more complicated than that, because the arbitrary keys aren't the top level of JSON; they only occur as a sub-object for prefs. The rest is known and can fit in a pre-defined class.
{
"al" : { "type": "admin", "prefs" : { "arbitrary_key_a":"arbitary_value_a", "arbitrary_key_b":"arbitary_value_b"}},
"bert" : {"type": "user", "prefs" : { "arbitrary_key_x":"arbitary_value_x", "arbitrary_key_y":"arbitary_value_y"}},
...
}
In Java, I want to be able to take that String, and do something like:
people.get("al").get("prefs"); // Returns Map<String, String>
How can I do this? I'd like to use a standard well-supported parser, avoid exceptions, and keep things simple.
UPDATE
#kumensa has pointed out that this is harder than it looks. Being able to do:
people.get("al").getPrefs(); // Returns Map<String, String>
people.get("al").getType(); // Returns String
is just as good.
That should parse the JSON to something like:
public class Person {
public String type;
public HashMap<String, String> prefs;
}
// JSON parsed to:
HashMap<String, Person>
Having your Person class and using Gson, you can simply do:
final Map<String, Person> result = new Gson().fromJson(json, new TypeToken<Map<String, Person>>() {}.getType());
Then, retrieving prefs is achieved with people.get("al").getPrefs();.
But be careful: your json string is not valid. It shouldn't start with "people:".
public static <T> Map<String, T> readMap(String json) {
if (StringUtils.isEmpty(json))
return Collections.emptyMap();
ObjectReader reader = new ObjectMapper().readerFor(Map.class);
MappingIterator<Map<String, T>> it = reader.readValues(json);
if (it.hasNextValue()) {
Map<String, T> res = it.next();
return res.isEmpty() ? Collections.emptyMap() : res;
}
return Collections.emptyMap();
}
All you need to do next, it that check the type of the Object. If it is Map, then you have an object. Otherwise, this is a simple value.
You can use Jackson lib to achieve this.
Put the following in pom.xml.
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.8</version>
</dependency>
Refer the following snippet that demonstrates the same.
ObjectMapper mapper = new ObjectMapper();
HashMap<String, Object> people = mapper.readValue(jsonString, new TypeReference<HashMap>(){});
Now, it is deserialized as a Map;
Full example:
import java.io.IOException;
import java.util.HashMap;
import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
public class testMain {
public static void main(String[] args) throws JsonParseException, JsonMappingException, IOException {
String json = "{\"address\":\"3, 43, Cashier Layout, Tavarekere Main Road, 1st Stage, BTM Layout, Ambika Medical, 560029\",\"addressparts\":{\"apartment\":\"Cashier Layout\",\"area\":\"BTM Layout\",\"floor\":\"3\",\"house\":\"43\",\"landmark\":\"Ambika Medical\",\"pincode\":\"560029\",\"street\":\"Tavarekere Main Road\",\"subarea\":\"1st Stage\"}}";
ObjectMapper mapper = new ObjectMapper();
HashMap<String, Object> people = mapper.readValue(json, new TypeReference<HashMap>(){});
System.out.println(((HashMap<String, String>)people.get("addressparts")).get("apartment"));
}
}
Output: Cashier Layout

Merge json payload values to Object

I want to merge the REST PATH payload to the 'Entity' object after getting it from a database, so that only the attributes provided in payload will be updated in entity. Hence, I want to ensure that only the attributes provided as part of patch payload will be updated safely.
I am using Spring Rest Controller with Hibernate entities.
#PatchMapping(value = "/{id}")
public Resource<DepartmentPEO> update(#PathVariable Long id,
#RequestBody JSONObject payload) throws Exception
{
DepartmentPEO eo = departmentService.getRow(id);
// Have to do something to update the eo object from jsonObject.
// Some api to update eo
eo = departmentService.update(id, eo);
Resource<DepartmentPEO> resource = new Resource<>(eo);
DepartmentPEO dept = resource.getContent();
id = dept.getDeptSeq();
resource.add(
linkTo(methodOn(DepartmentsRestController.class).getRow(id))
.withSelfRel());
return resource;
}
Only the modified attributes will be sent as part of payload to server instead of sending all attributes.Resource(entity) will have nested list of objects (One-to-many). Am looking for the pool-proof solution for this use case and also believe this is common/basic for every rest api supported apps.
Pointing to any API to solve this would greatly appreciated!
Thank you
Here is a working example using Jackson's ObjectMapper and BeanUtils from Spring (since I assume you're using Spring) :
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;
import org.json.JSONException;
import org.json.JSONObject;
import org.junit.Test;
import org.springframework.beans.BeanUtils;
public class StackOverflow {
#Test
public void mergeTest() throws IOException, JSONException {
DepartmentPEO existingDepartement = existingDepartmentPEO();
JSONObject payload = new JSONObject();
payload.put("name", "newName");
DepartmentPEO result = mergeToObject(payload, existingDepartement);
assert result.getName().equals("newName");
assert result.getId().equals("1");
}
private DepartmentPEO existingDepartmentPEO() {
DepartmentPEO existingDepartement = new DepartmentPEO();
existingDepartement.setId("1");
existingDepartement.setName("oldName");
return existingDepartement;
}
private DepartmentPEO mergeToObject(JSONObject payload, DepartmentPEO object) throws IOException {
ObjectMapper objectMapper = new ObjectMapper();
DepartmentPEO updateRequest = objectMapper.readValue(payload.toString(), DepartmentPEO.class);
BeanUtils.copyProperties(updateRequest, object, "id");
return object;
}
}
Here, I transform the JSONObject into a DepartmentPEO class then I copy this object into the existing one ignoring the field id.
You may want to have a generic way to ignore null fields from the JSONObject, then you can refer to this post for instance How to ignore null values using springframework BeanUtils copyProperties?
I would advice to send directly the DepartmentPEO object into the REST method signature instead of using a JSONObject.
Regards

Using Jackson to deserialize into a Map

I have a JSON object with two attributes: "key" which is a string, and "value" which can be deserialized into a Java bean.
{ "key": "foo", "value": "bar" }
The question is, given a list of such objects, can I deserialize it into a Map?
[{"key": "foo1", "value": "bar1"}, {"key": "foo2", "value": "bar2"}] -> Map<String, String>
Currently using Jackson-databind 2.1
You can easily convert above JSON to List<Map<String, String>>:
import java.util.List;
import java.util.Map;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.type.CollectionType;
public class JacksonProgram {
public static void main(String[] args) throws Exception {
ObjectMapper mapper = new ObjectMapper();
CollectionType mapCollectionType = mapper.getTypeFactory().constructCollectionType(List.class, Map.class);
List<Map<String, String>> result = mapper.readValue(json, mapCollectionType);
System.out.println(result);
}
}
Above program prints:
[{key=foo1, value=bar1}, {key=foo2, value=bar2}]
Since your structure does not match, you have two basic options:
Use two-phase handling: use Jackson to bind into intermediate representation that does match (which could be JsonNode or List<Map<String,Object>>), and then do conversion to desired type with simple code
Write custom serializer and/or deserializer
Jackson does not support extensive structural transformations (there are some simple ones, like #JsonUnwrapped), so this kind of functionality is unlikely to be added to databind module. Although it could be added as an extension module, if these "smart Map" types of structures are commonly used (they seem to be, unfortunately).
Had the same problem and was surprised Jackson wasn't able to natively handle it. Solution I went with was to create a custom setter on the object I was trying to marshal into :
public class somePojo {
private Map<String, String> mapStuff;
...
public void SetMapStuff(List<Map<String, String> fromJackson){
mapStuff= new HashMap<>();
for (Map<String, String> pair : fromJackson) {
put(pair.get("key"), pair.get("value"));
}
}
}
Jackson is smart enough to find that setter to and can happily pass it the List.

Categories

Resources