How to map a Json attribute name to a Java field value - java

How to map a JSON attribute name to Java field value using jackson?
This:
{ "list":[ { "monday":[ "apple", "bread"] } ,
{ "sunday":[ "bacon", "beer" ] } ],
"kind":"shoplist" }
to this:
public class StuffList {
public List<Stuff> list;
public String kind;
}
public class Stuff {
public String name;
public List<String> items;
}
The fragment "monday":[ "apple", "bread"] is mapped to two variables, one with the attribute name and another with the attribute value.

Your JSON represets simple list of maps, more exactly: List<Map<String, List<String>>>. You can convert this JSON into this POJO:
class JsonEntity {
public List<Map<String, List<String>>> list;
public String kind;
public StuffList toStuffList() {
StuffList stuffList = createStuffListObject();
return stuffList;
}
private StuffList createStuffListObject() {
StuffList stuffList = new StuffList();
stuffList.kind = kind;
stuffList.list = createItemsList();
return stuffList;
}
private List<Stuff> createItemsList() {
List<Stuff> items = new ArrayList<Stuff>(list.size());
for (Map<String, List<String>> item : list) {
items.add(convertToStuff(item));
}
return items;
}
private Stuff convertToStuff(Map<String, List<String>> item) {
Stuff stuff = new Stuff();
stuff.name = item.keySet().iterator().next();
stuff.items = item.values().iterator().next();
return stuff;
}
}
And now we can deserialize JSON in this way:
public static void main(String[] args) throws Exception {
String json = "{\"list\":[{\"monday\":[\"apple\", \"bread\"]},{\"sunday\":[\"bacon\", \"beer\"]} ],\"kind\":\"shoplist\"}";
ObjectMapper objectMapper = new ObjectMapper();
JsonEntity jsonEntity = objectMapper.readValue(json, JsonEntity.class);
System.out.println(jsonEntity.toStuffList());
}
Output of the program:
list=[[name=monday, items=[apple, bread]], [name=sunday, items=[bacon, beer]]], kind=shoplist

Use annotation #JsonProperty If you want to change name use annotation with argument, e.g. #JsonProperty("stuff_name")

Related

Deserialize a JSON payload to object base on JSON integer property

I have below classes:
public class Result<T> {
public int code;
public Object meta;
public T data;
}
public class User {
public int id;
public String name;
}
public class Error {
public String field;
public String message;
}
I want to deserialize a JSON payload based on code field. If code >= 10, return Result<ArrayList<Error>>, otherwise return Result<User>
Currently, I map JSON to Result<Object> first, then check the code field. Based on that value I make second map to desired object.
ObjectMapper mapper = new ObjectMapper();
Result<Object> tempResult = mapper.readValue(json, new TypeReference<Result<Object>>() {});
if (tempResult.code < 10) {
Result<User> result = mapper.readValue(json, new TypeReference<Result<User>>() {});
return result;
} else {
Result<ArrayList<Error>> result = mapper.readValue(json, new TypeReference<Result<ArrayList<Error>>>() {});
return result;
}
Is there an elegant way to do this without deserializing it 2 times?
You need to implement custom TypeIdResolver:
class UserTypeIdResolverBase extends TypeIdResolverBase {
#Override
public String idFromValue(Object value) {
throw new IllegalStateException("Not implemented!");
}
#Override
public String idFromValueAndType(Object value, Class<?> suggestedType) {
throw new IllegalStateException("Not implemented!");
}
#Override
public JsonTypeInfo.Id getMechanism() {
return JsonTypeInfo.Id.CUSTOM;
}
#Override
public JavaType typeFromId(DatabindContext context, String id) {
if (Integer.parseInt(id) < 10) {
return context.getTypeFactory().constructType(new TypeReference<Result<User>>() {});
}
return context.getTypeFactory().constructType(new TypeReference<Result<List<Error>>>() {});
}
}
and declare it for a Result class:
#JsonTypeInfo(property = "code", use = JsonTypeInfo.Id.CUSTOM, visible = true)
#JsonTypeIdResolver(UserTypeIdResolverBase.class)
class Result<T>

Java Jackson deserialize an object containing a list of object with/without custom Deserializer?

I've got a JSON input like this
{
"slices": [{
"slice": {
"boundedBy": {
"Envelope": {
"axisLabels": "Lat Long ansi",
"lowerCorner": "-44.975 111.975 \"2003-01-01T00:00:00+00:00\"",
"upperCorner": "-8.975 155.975 \"2003-01-01T00:00:00+00:00\"",
"srsDimension": 3
}
},
"fileReferenceHistory": "/home/rasdaman/rasdaman_community/rasdaman/systemtest/testcases_services/test_all_wcst_import/testdata/wcs_local_metadata_tiff_no_specify_bands/GlobLAI-20030101-20030110-H01V06-1.0_MERIS-FR-LAI-HA.tiff",
"local_metadata_key": "value_1"
}
},
{
"slice": {
"boundedBy": {
"Envelope": {
"axisLabels": "Lat Long ansi",
"lowerCorner": "-44.975 111.975 \"2003-10-01T00:00:00+00:00\"",
"upperCorner": "-8.975 155.975 \"2003-10-01T00:00:00+00:00\"",
"srsDimension": 3
}
},
"fileReferenceHistory": "/home/rasdaman/rasdaman_community/rasdaman/systemtest/testcases_services/test_all_wcst_import/testdata/wcs_local_metadata_tiff_no_specify_bands/GlobLAI-20031001-20031010-H00V10-1.0_MERIS-FR-LAI-HA.tiff",
"local_metadata_key": "value_2"
}
}
],
"Title": "Drought code",
// other keys:values
}
with "slices" is an array of "slice" objects. Out of "slices" is any "keys":"values" but it is not the problem.
Then, I have a POJO class
public class CoverageMetadata {
#JsonProperty(value = "slices")
#JacksonXmlElementWrapper(useWrapping = false)
private List<LocalMetadata> localMetadataList;
private Map<String, String> globalMetadataAttributesMap;
#JsonAnySetter
public void addKeyValue(String key, String value) {
this.globalMetadataAttributesMap.put(key, value);
}
#JsonAnyGetter
public Map<String, String> getGlobalAttributesMap() {
return globalMetadataAttributesMap;
}
// other gettters, setters without Jackson annotations
}
and a class inside the list:
public class LocalMetadata {
public static final String LOCAL_METADATA_TAG = "slice";
private Map<String, String> localMetadataAttributesMap;
private BoundedBy boundedBy;
#JsonAnySetter
// NOTE: To map an unknown list of properties, must use this annotation
public void addKeyValue(String key, String value) {
this.localMetadataAttributesMap.put(key, value);
}
public LocalMetadata() {
this.localMetadataAttributesMap = new LinkedHashMap<>();
this.boundedBy = new BoundedBy();
}
#JsonAnyGetter
// NOTE: to unwrap the "map" from { "map": { "key": "value" } }, only keep { "key": "value" }
public Map<String, String> getLocalMetadataAttributesMap() {
return localMetadataAttributesMap;
}
public BoundedBy getBoundedBy() {
return this.boundedBy;
}
public void setBoundedBy(BoundedBy boundedBy) {
this.boundedBy = boundedBy;
}
public LocalMetadata(Map<String, String> localMetadataAttributesMap, BoundedBy boundedBy) {
this.localMetadataAttributesMap = localMetadataAttributesMap;
this.boundedBy = boundedBy;
}
}
And the basic code to deserialize JSON to object
ObjectMapper objectMapper = new ObjectMapper();
CoveageMetadata coverageMetadata = objectMapper.readValue(metadata, CoverageMetadata.class);
When I try to deserialize the JSON input to CoverageMetadata object, I got the error
Cannot deserialize coverage's metadata in XML/JSON by Jackson, error: Can not deserialize instance of java.lang.String out of START_OBJECT token
at [Source: {"slices":[{"slice":{"boundedBy":{"Envelope":{"axisLabels":"Lat Long ansi","srsDimension":3,"lowerCorner":"-44.975 111.975 \"2003-01-01T00:00:00+00:00\"","upperCorner":"-8.975 155.975 \"2003-01-01T00:00:00+00:00\""}},"local_metadata_key":"value_1","fileReferenceHistory":"/home/rasdaman/rasdaman_community/rasdaman/systemtest/testcases_services/test_all_wcst_import/testdata/wcs_local_metadata_tiff_no_specify_bands/GlobLAI-20030101-20030110-H01V06-1.0_MERIS-FR-LAI-HA.tiff"}}],"Title":"Drought code"}; line: 1, column: 21] (through reference chain: petascope.core.gml.metadata.model.CoverageMetadata["slices"]->java.util.ArrayList[0]->petascope.core.gml.metadata.model.LocalMetadata["slice"]).
How can I deserialize this JSON input String to CoverageMetadataObject with each "slice" element will be mapped to a LocalMetadata object?
The simple answer is I create another POJO class to hold the "slices" list, in CoverageMetadata class, it will have
public class CoverageMetadata {
private Map<String, String> globalMetadataAttributesMap;
#JsonProperty(value = "slices")
private LocalMetadata localMetadata;
...
}
New POJO class (class LocalMetadata before was renamed to LocalMetadataChild)
public class LocalMetadata {
#JsonProperty(value = "slice")
// This is the most important thing to avoid duplicate <slices><slices> when serializing in XML.
#JacksonXmlElementWrapper(useWrapping = false)
private List<LocalMetadataChild> localMetadataList;
public LocalMetadata(List<LocalMetadataChild> localMetadataList) {
this.localMetadataList = localMetadataList;
}
public LocalMetadata() {
this.localMetadataList = new ArrayList<>();
}
public List<LocalMetadataChild> getLocalMetadataList() {
return localMetadataList;
}
public void setLocalMetadataList(List<LocalMetadataChild> localMetadataList) {
this.localMetadataList = localMetadataList;
}
}

Jackson serialization key and value from one object

I am trying to create a custom serializer to generate Pair object but I want to avoid "key" and "value" fields when the object is serialized.
Object pojo:
public class TypeObjectPair implements Serializable {
private final String canonicalObjectName;
private final Object object;
public String getKey() {
return canonicalObjectName;
}
public Object getValue() {
return object;
}
}
Person class. (Theoretically could be any other object class)
class Person{
int id;
String name;
}
Final object to serialize:
TypeObjectPair obj = new TypeObjectPair("com.example.Person", new Person(1, "Peter"));
Required output:
{
"com.example.object" : {
"id" : 1,
"name" : "Peter"
}
}
Any ideas on how to achieve it?
You could use a Map<K, V> with #JsonAnyGetter:
public class TypeObjectPair {
private Map<String, Object> data = new HashMap<>();
public TypeObjectPair(String key, Object value) {
data.put(key, value);
}
#JsonAnyGetter
public Map<String, Object> getData() {
return data;
}
}
Then use as follows:
ObjectMapper mapper = new ObjectMapper();
TypeObjectPair pair = new TypeObjectPair("com.example.object", new Person(1, "Peter"));
String json = mapper.writer().withDefaultPrettyPrinter().writeValueAsString(pair);
The output will be:
{
"com.example.object" : {
"id" : 1,
"name" : "Peter"
}
}

Jackson catch unrecognized field in a map

I'm using Jackson in a java Rest Api to handle request params.
My Bean class :
public class ZoneModifBeanParam extends ModifBeanParam<Zone> {
#FormParam("type")
private String type;
#FormParam("geometry")
private Geometry geometry;
#FormParam("name")
private String name;
...
My API interface :
#POST
#Consumes("application/json")
#Produces("application/json; subtype=geojson")
#ApiOperation(value = "Create a zone", notes = "To create a zone")
public Response createZone(ZoneModifBeanParam zoneParam) {
...
This Works fine but I need to receive other params that aren't specified by my Bean in a Map.
Example :
{
"geometry": {...},
"name": "A circle name",
"type": "4",
"hello": true
}
By receiving this I need to store in a Map (named unrecognizedFields and declared in my bean) the couple ("hello", true).
Is there any annotation or object allowing this?
Just use #JsonAnySetter. That's what it's made for. Here is a test case
public class JacksonTest {
public static class Bean {
private String name;
public String getName() { return this.name; }
public void setName(String name) { this.name = name; }
private Map<String, Object> unrecognizedFields = new HashMap<>();
#JsonAnyGetter
public Map<String, Object> getUnrecognizedFields() {
return this.unrecognizedFields;
}
#JsonAnySetter
public void setUnrecognizedFields(String key, Object value) {
this.unrecognizedFields.put(key, value);
}
}
private final String json
= "{\"name\":\"paul\",\"age\":600,\"nickname\":\"peeskillet\"}";
private final ObjectMapper mapper = new ObjectMapper();
#Test
public void testDeserialization() throws Exception {
final Bean bean = mapper.readValue(json, Bean.class);
final Map<String, Object> unrecognizedFields = bean.getUnrecognizedFields();
assertEquals("paul", bean.getName());
assertEquals(600, unrecognizedFields.get("age"));
assertEquals("peeskillet", unrecognizedFields.get("nickname"));
}
}
The #JsonAnyGetter is used on the serialization side. When you serialize the bean, you will not see the unrecognizedFields in the JSON. Instead all the properties in the map will be serialized as top level properties in the JSON.
You may be able to ignore the unrecognized fields safely by configuring the ObjectMapper, however to specifically put them as key-value pairs of a Map field, you'll need your own de-serializer.
Here's a (heavily simplified) example:
Given your POJO...
#JsonDeserialize(using=MyDeserializer.class)
class Foo {
// no encapsulation for simplicity
public String name;
public int value;
public Map<Object, Object> unrecognized;
}
... and your custom de-serializer...
class MyDeserializer extends JsonDeserializer<Foo> {
#Override
public Foo deserialize(JsonParser p, DeserializationContext ctxt)
throws IOException, JsonProcessingException {
// new return object
Foo foo = new Foo();
// setting unrecognized container
Map<Object, Object> unrecognized = new HashMap<>();
foo.unrecognized = unrecognized;
// initializing parsing from root node
JsonNode node = p.getCodec().readTree(p);
// iterating node fields
Iterator<Entry<String, JsonNode>> it = node.fields();
while (it.hasNext()) {
Entry<String, JsonNode> child = it.next();
// assigning known fields
switch (child.getKey()) {
case "name": {
foo.name = child.getValue().asText();
break;
}
case "value": {
foo.value = child.getValue().asInt();
break;
}
// assigning unknown fields to map
default: {
foo.unrecognized.put(child.getKey(), child.getValue());
}
}
}
return foo;
}
}
Then, somewhere...
ObjectMapper om = new ObjectMapper();
Foo foo = om.readValue("{\"name\":\"foo\",\"value\":42,\"blah\":true}", Foo.class);
System.out.println(foo.unrecognized);
Output
{blah=true}

Jackson deserializing hash map

I have the following JSON:
"propertyName": "{"1":[{"1":"value1","2":"value2","3":false}]}"
the first property being the count of items in the array following having a map of properties.
What is the best way to deserialize this using Jackson
if I want to fill up a class holding these values:
class MyHolder
{
name = "value1";
age = "value2";
female = false;
}
for instance.
To deserialize to list/collection of concrete values (rather then the LinkedHashMap you get by default) you will need to pass type information to Jackson:
mapper.readValue(jsonString, new TypeReference<List<MyHolder>>() { });
The other way to do the same is:
CollectionType javaType = mapper.getTypeFactory().constructCollectionType(List.class, MyHolder.class);
List<MyDto> asList = mapper.readValue(jsonString, javaType);
Hope this helps.
Your JSON is not valid. Let assume that JSON looks like this:
{
"propertyName":{
"1":[
{
"1":"value1",
"2":"value2",
"3":false
}
]
}
}
The simplest way is to create POJO classes which fit to your JSON. For example:
class Root {
private Map<String, List<MyHolder>> propertyName;
//getters,setters,toString
}
class MyHolder {
#JsonProperty("1")
private String name;
#JsonProperty("2")
private String age;
#JsonProperty("3")
private boolean female;
//getters,setters,toString
}
Now we can easily deserialize it in this way:
ObjectMapper mapper = new ObjectMapper();
System.out.println(mapper.readValue("{.. json ...}", Root.class));
Above program prints:
Root [propertyName={1=[MyHolder [name=value1, age=value2, female=false]]}]
If we do not want to see Map in our POJO class we have to write custom converter:
class MapMyHolderConverter implements Converter<Map<String, List<Map<String, Object>>>, List<MyHolder>> {
#Override
public JavaType getInputType(TypeFactory typeFactory) {
return typeFactory.constructMapType(Map.class, String.class, List.class);
}
#Override
public JavaType getOutputType(TypeFactory typeFactory) {
return typeFactory.constructCollectionType(List.class, MyHolder.class);
}
#Override
public List<MyHolder> convert(Map<String, List<Map<String, Object>>> map) {
Collection<List<Map<String, Object>>> values = map.values();
if (values.isEmpty()) {
return Collections.emptyList();
}
List<MyHolder> result = new ArrayList<>();
for (Map<String, Object> item : values.iterator().next()) {
MyHolder holder = new MyHolder();
holder.setName(item.get("1").toString());
holder.setAge(item.get("2").toString());
holder.setFemale((Boolean) item.get("3"));
result.add(holder);
}
return result;
}
}
Your POJO classes could look like this now:
class Root {
#JsonDeserialize(converter = MapMyHolderConverter.class)
private List<MyHolder> propertyName;
//getters,setters,toString
}
class MyHolder {
private String name;
private String age;
private boolean female;
//getters,setters,toString
}
As you can see in second example we are using #JsonDeserialize annotation and we have not to use #JsonProperty.

Categories