Deserialize JSON array to Map using Jackson - java

I have JSON structured like:
{
"id" : "123",
"name" : [ {
"stuff" : [ {
"id" : "234",
"name" : "Bob"
}, {
"id" : "345",
"name" : "Sally"
} ]
} ]
}
That I want to map to the following data structure:
MyInterface1
#Value.Immutable
#JsonSerialize(as = ImmutableMyInterface1.class)
#JsonDeserialize(as = ImmutableMyInterface1.class)
public interface MyInterface1 {
String id();
#JsonDeserialize(using = MyInterface1Deserializer.class)
List<MyInterface2> name();
}
MyInterface2
#Value.Immutable
#JsonSerialize(as = ImmutableMyInterface2.class)
#JsonDeserialize(as = ImmutableMyInterface2.class)
public interface MyInterface2 {
#JsonDeserialize(using = StuffDeserializer.class)
Map<String, MyInterface3> stuff();
}
MyInterface3
#Value.Immutable
#JsonSerialize(as = ImmutableMyInterface3.class)
#JsonDeserialize(as = ImmutableMyInterface3.class)
public interface MyInterface3 {
String id();
String name();
}
I'm using an ObjectMapper with readValue(stringWithJson,MyInterface1.class) to map this JSON to MyInterface1, which should continue down the chain to MyInterface3. This setup was working when I was using a List in MyInterface2, i.e. List<MyInterface3> name();
However, I want this to be a map instead of a list, ideally with "id" from the inner JSON as the key. This would allow me to get values with the following syntax:
MyInterface1.get(0).MyInterface2.get("id1").name();
The problem is that when attempting to create a custom StuffDeserializer.class, I'm getting the error:
Can not deserialize instance of com.foo.ImmutableMyInterface2$Json out of START_ARRAY token
when trying to do:
public Map<String, MyInterface3> deserialize(JsonParser jsonParser, DeserializationContext ctxt)
throws IOException {
MyInterface2 foo = Unmarshaller.OBJECT_MAPPER.readValue(jsonParser, MyInterface2.class); // error here
...
I think this is because Jackson is expecting "stuff" to be a List 'cause of the JSON array. What's the best way to deserialize this JSON to a map that uses values from the inner JSON as a key?

I would create a custom JsonDeserializer to map id and name into a map:
public class StringHashMapValueDeserializer extends JsonDeserializer<HashMap<String, String>>{
#Override
public HashMap<String, String> deserialize(JsonParser parser, DeserializationContext ctxt)
throws IOException, JsonProcessingException {
HashMap<String, String> ret = new HashMap<String, String>();
ObjectCodec codec = parser.getCodec();
TreeNode node = codec.readTree(parser);
if (node.isArray()){
for (JsonNode n : (ArrayNode)node){
JsonNode id = n.get("id");
if (id != null){
JsonNode name = n.get("name");
ret.put(id.asText(), name.asText());
}
}
}
return ret;
}
}
And then I would create simple beans with annotating stuff property with the deserializer:
#Getter
#Setter
public class Name {
#JsonDeserialize(using = StringHashMapValueDeserializer.class)
Map<String, String> stuff;
#Override
public String toString() {
return "Name [stuff=" + stuff + "]";
}
}
Outer type:
#Getter
#Setter
public class OuterType {
String id;
List<Name> name;
#Override
public String toString() {
return "OuterType [id=" + id + ", stuff=" + name + "]";
}
}
Deserialization:
ObjectMapper mapper = new ObjectMapper();
OuterType response;
response = mapper.readValue(json, OuterType.class);
System.out.println(response);
System.out.println(response.getName().get(0).getStuff().get("234"));
console output:
OuterType [id=123, stuff=[Name [stuff={234=Bob, 345=Sally}]]]
Bob
Hope it helps.

Related

Jackson #JsonValue is conflicting with #JsonTypeInfo; How to make them work together

I am using the Jackson for serialization of a POJO. This POJO class consists of some fields and a Map<String,Object> others. I am using the Custom Serializer while writing JSON for this MAP field. I want to avoid getting the Map field name "others"ยด in my JSON. Hence, I am using the #JsonValueon theMapfield but using the#JsonValueis conflicting with#JsonTypeInfo`. I need both annotations in my class how can I achieve this?
As of now, I am getting the JSON as following: (With both #JsonValue and #JsonTypeInfo)
[ "Customer", {
"name" : "Rise Against",
"google:sub" : "MyValue-1",
"age" : "2000"
} ]
I would like to get the JSON as following with both #JsonValue and #JsonTypeInfo: (As you can see the others key is committed but its values are added to the JSON directly)
{
"isA" : "Customer",
"name" : "Batman",
"google:sub" : "MyValue-1",
"age" : "2008"
}
I am able to get the output but I need to remove the annotation from my class:
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, visible = true, property = "isA")
But if I remove this then I will not get the isA property in my JSON. I want to know how to make the Jackson Json Serializer work both with the #JsonTypeInfo and #JsonValue.
Output without #JsonTypeInfo but with #JsonValue:
{
"name" : "Rise Against",
"google:sub" : "MyValue-1",
"age" : "2000"
}
Output without #JsonValue but with #JsonTypeInfo
{
"isA" : "Customer",
"name" : "",
"age" : "",
"others" : {
"name" : "Rise Against",
"google:sub" : "MyValue-1",
"age" : "2000"
}
}
Following is my Customer class Pojo:
#XmlRootElement(name = "Customer")
#XmlType(name = "Customer", propOrder = {"name", "age", "others"})
#XmlAccessorType(XmlAccessType.FIELD)
#NoArgsConstructor
#Getter
#Setter
#JsonInclude(JsonInclude.Include.NON_NULL)
#AllArgsConstructor
#JsonIgnoreType
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = As.PROPERTY, visible = true, property = "isA")
public class Customer {
#XmlElement(name = "name", required = true)
private String name;
#XmlElement(name = "age", required = true)
private String age;
#JsonSerialize(using = CustomExtensionsSerializer.class)
#XmlJavaTypeAdapter(TestAdapter.class)
#XmlPath(".")
#JsonValue
private Map<String, Object> others = new HashMap<>();
#JsonAnySetter
public void setOthers(String key, Object value) {
others.put(key, value);
}
public Map<String, Object> getOthers() {
return others;
}
}
Following is my `Custom serializer:
public class CustomExtensionsSerializer extends JsonSerializer<Map<String, Object>> {
private static final ObjectMapper mapper = new ObjectMapper();
#Override
public void serialize(Map<String, Object> value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
gen.writeStartObject();
recusiveSerializer(value, gen, serializers);
gen.writeEndObject();
}
public void recusiveSerializer(Map<String, Object> value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
for (Map.Entry<String, Object> extension : value.entrySet()) {
if (extension.getValue() instanceof Map) {
//If instance is MAP then call the recursive method
recusiveSerializer((Map) extension.getValue(), gen, serializers);
} else if (extension.getValue() instanceof String) {
//If instance is String directly add it to the JSON
gen.writeStringField(extension.getKey(), (String) extension.getValue());
} else if (extension.getValue() instanceof ArrayList) {
//If instance if ArrayList then loop over it and add it to the JSON after calling recursive method
gen.writeFieldName(extension.getKey());
gen.writeStartObject();
for (Object dupItems : (ArrayList<String>) extension.getValue()) {
if (dupItems instanceof Map) {
recusiveSerializer((Map) dupItems, gen, serializers);
} else {
gen.writeStringField(extension.getKey(), (String) extension.getValue());
}
}
gen.writeEndObject();
}
}
}
}
Try to set #JsonTypeInfo.include to JsonTypeInfo.As.EXISTING_PROPERTY
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.EXISTING_PROPERTY, visible = true, property = "isA")

Jackson WRAP_ROOT_VALUE adds two root elements

I'm writing a REST API in Java and Play Framework, however I ran into a problem with Jackson serialization. I have the following model:
#Entity
#JsonRootName("country")
public class Country extends BaseModel<Country> {
private String name;
private Collection<City> cities;
...
}
The Jackson object mapper configuration:
ObjectMapper mapper = Json.newDefaultMapper()
.configure(SerializationFeature.WRAP_ROOT_VALUE, true)
.configure(SerializationFeature.INDENT_OUTPUT, true);
When I serialize a Country model however,
Country c = service.get(id);
return ok(toJson(c));
I get the following output:
{
"ObjectNode" : {
"country" : {
"id" : 5,
"name" : "Holandija",
"cities" : [ ]
}
}
}
The expected output would be:
{
"country" : {
"id" : 5,
"name" : "Holandija",
"cities" : [ ]
}
}
Why is Jackson adding the extra ObjectNode node? How to get rid of it?
It seems you have a problem in toJson method. The following code works perfect (the original class Country was modified for simplicity):
#Entity
#JsonRootName(value = "country")
public class Country {
public int id;
public String name;
public Collection<String> cities;
public Country() {
}
public Country(int id, String name) {
this.id = id;
this.name = name;
}
}
Test:
#Test
public void testRootJsonMapping() throws JsonProcessingException {
Country tested = new Country(55, "Neverland");
ObjectMapper mapper = new ObjectMapper()
.configure(SerializationFeature.WRAP_ROOT_VALUE, true)
.configure(SerializationFeature.INDENT_OUTPUT, true);
String json = mapper.writeValueAsString(tested);
System.out.println("json:" + json);
}
Test output:
json:{
"country" : {
"id" : 55,
"name" : "Neverland",
"cities" : null
}
}
If json conversion is done with Play API Json, it should be configured on startup with appropriate mapping options:
private void configureJson() {
ObjectMapper mapper = new ObjectMapper()
.configure(SerializationFeature.WRAP_ROOT_VALUE, true)
.configure(SerializationFeature.INDENT_OUTPUT, true);
Json.setObjectMapper(mapper);
}
Here you can read more details of how to customize Json conversion in Play.

How to treat JSON Value as String Object in Java using ObjectMapper?

I am facing issue when converting Json to Java Object.
My "jsonText" field have json as value which i want to be placed in String. My custom Class hass following structure.
Class Custom{
#JsonProperty(value = "field1")
private String field1;
#JsonProperty(value = "jsonText")
private String jsonText;
}
Below is my code:
ObjectMapper mapper = new ObjectMapper();
JsonNode node = mapper.readTree(inputString);
String nodeTree = node.path("jsonText").toString();
List<PatientMeasure> measuresList =mapper.readValue(nodeTree,
TypeFactory.defaultInstance().constructCollectionType(ArrayList.class, CustomClass.class) );
Json to convert is :
"field1" : "000000000E",
"jsonText" : {
"rank" : "17",
"status" : "",
"id" : 0
}
Exception got:
org.codehaus.jackson.map.JsonMappingException: Can not deserialize instance of java.lang.String out of START_OBJECT token
at [Source: java.io.StringReader#3362f02f; line: 1, column: 108] (through reference chain: com.Custom["jsonText"])
You can try this:
JSONArray ar= new JSONArray(result);
JSONObject jsonObj= ar.getJSONObject(0);
String strname = jsonObj.getString("NeededString");
You can use a custom deserializer like this:
public class AnythingToString extends JsonDeserializer<String> {
#Override
public String deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {
TreeNode tree = jp.getCodec().readTree(jp);
return tree.toString();
}
}
And then annotate your field to use this deserializer:
class Custom{
#JsonProperty(value = "field1")
private String field1;
#JsonProperty(value = "jsonText")
#JsonDeserialize(using = AnythingToString.class)
private String jsonText;
}

How to deserialize a list as a single objects instead of a list object?

I have the following Address and AddressList classes
public class Address {
private String street;
private String city;
private String state;
// ...
}
public class AddressList {
private List<Address> addresses;
// ...
}
and then a Person class
public class Person {
private String name;
#JsonDeserialize(contentUsing = ListDeserializer.class)
private Map<String, AddressList> addresses;
// ..
}
Then I have the Yaml file like this
---
name: 'abc'
addresses:
offices:
- street: 123 main st
city: san francisco
state: ca
- street: 234 post st
city: san francisco
state: ca
My List deserializer class is as follows:
public class ListDeserializer extends JsonDeserializer<AddressList> {
#Override
public AddressList deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException {
return jsonParser.readValueAs(new TypeReference<Map<String, List<Address>>>() {
});
}
}
my parsing code is as follows:
ObjectMapper objectMapper = new ObjectMapper(new YAMLFactory());
return objectMapper.readValue(inputYamlFile, Person.class);
when I read the list of addresses: it prints as
{offices=null}
Can anyone help with resolving this?
If it was serialized as a list you have to deserialize it the same way. But you then have the list and and can iterate it yourself.
I found a way to resolve this issue using deserializing addresses map using Converter.
The modifications I made are
public class Person {
private String name;
#JsonDeserialize(converter = AddressListConverter.class)
private Map<String, AddressList> addresses;
// ..
}
I, then, wrote a new converter class for the AddressList.
public class AddressListConverter implements Converter<Map<String, List<LinkedHashMap>>, Map<String, AddressList>> {
#Override
public Map<String, AddressList> convert(Map<String, List<LinkedHashMap>> stringListMap) {
Map<String, AddressList> addressListMap = new HashMap<>();
ObjectMapper mapper = new ObjectMapper();
for (Map.Entry<String, List<LinkedHashMap>> entry : stringListMap.entrySet()) {
AddressList addressList = new AddressList();
for(LinkedHashMap map: entry.getValue()) {
Address address = mapper.convert(map, Address.class);
addressList.getAddresses().add(address);
}
addressListMap.put(entry.getKey(), addressList);
}
return addressListMap;
}
#Override
public JavaType getInputType(TypeFactory typeFactory) {
return typeFactory.constructMapType(Map.class, String.class, List.class);
}
#Override
public JavaType getOutputType(TypeFactory typeFactory) {
return typeFactory.constructMapType(Map.class, String.class, AddressList.class);
}
}
This should do the trick

Passing parameters to custom Deserializer

I have json that contains multiple rows.
{
"rows": [{
"aId": 408,
"aFrstNm": "TIM",
},
{
"aId": 410,
"aFrstNm": "BOB",
},
{
"aId": 409,
"aFrstNm": "HENRY",
}]
}
and POJOs
public class User extends Employee {
#JsonProperty("tableName")
private Double aID;
#JsonProperty("aFrstNm")
private String aFrstNm;
getters and setters ommitted
}
public class Employee {
}
public class Employees {
private Collection<? extends Employee> rows;
getters and setters ommitted
}
What I am trying to do is to pass the User class to a custom deserializer to be able return employees as a collection of Users that extend Employee:
SimpleModule simpleModule = new SimpleModule("MyModule")
.addDeserializer(Employees.class, new JsonDeserializer<Employees>() {
#Override
public Employees deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
ObjectMapper mapper = (ObjectMapper) p.getCodec();
final Class<?> clazz = (Class<?>) ctxt.getAttribute("mappingClass");
final JsonNode jsonNode = (JsonNode) mapper.readTree(p).get("rows");
Employees employees = new Employees();
Collection<Product> rows = new ArrayList();
if (jsonNode.isArray()) for (final JsonNode objNode : jsonNode) {
boolean isValueNode = objNode.isValueNode();
String json = mapper.writeValueAsString(objNode);
Product product = (Product) new ObjectMapper().readValue(json, clazz);
rows.add(product);
System.out.println(jsonNode);
}
employees.setRows(rows);
return employees;
}
});
mapper.registerModule(simpleModule);
the variable clazz (final Class clazz) attribute should contain the class a parameter. I so far have been unable to find a mechanism that allows this. DeserializationContext seems to be promising but I have not been able to make this work. Any suggestions on using DeserializationContext or some other way to pass parameters?
Tnx.

Categories

Resources