Jackson Deserialize Key/Value List into List<Class> - java

I have a response from web service call like this
// other key/values
properties: {
1000:true
2000:false
2939:true
...
}
// other key/values
I want to deserialize it to a POJO List which is
public class Service implements Serializable {
private Integer id;
private Boolean readOnly;
...
}
So far i have tried
ObjectMapper mapper = new ObjectMapper();
user.setServices(mapper.convertValue(response.getProperties(), Service.class));
But it doesn't convert map directly to the List. How do i achieve this? Here are the details:
Where response.getProperties() returns
#JsonAnyGetter
public Map<Integer, Boolean> getProperties() {
return properties;
}
And user.setServices
public void setServices(List<Service> services) {
this.services = services;
}

Related

Parse Json into POJO using Jackson

I have the following json
{
"root": {
"status": "UP",
"connection1": {
"status": "UP"
},
"connection2": {
"status": "UP"
}
}
}
Also i have the following POJO classes i want to convert JSON into
#JsonIgnoreProperties(ignoreUnknown = true)
public class POJO {
#JsonProperty("root")
#JsonDeserialize(using = RootDeserializer.class)
private Root root;
//getters + setters
}
public class Root {
private boolean isAlive;
private List<Connection> connections;
public Root(boolean isAlive, List<Connection> connections) {
this.isAlive = isAlive;
this.connections = connections;
}
//getters + setters
}
#JsonIgnoreProperties(ignoreUnknown = true)
public class Connection {
private String status;
//getters + setters
}
And finally i have this deserializer to convert json into Root instance
public class RootDeserializer extends JsonDeserializer<Root> {
private static final String CONNECTION_PREFIX = "connection";
private static final String UP_STATUS = "UP";
private ObjectMapper objectMapper = new ObjectMapper();
#Override
public Root deserialize(JsonParser parser, DeserializationContext context) throws IOException {
Map<String, Map<String, Object>> rootJsonMap = parser.readValueAs(Map.class);
boolean isAlive = StringUtils.equals(UP_STATUS, String.valueOf(rootJsonMap.get("status")));
List<Connection> connections = rootJsonMap.entrySet()
.stream()
.filter(entry -> StringUtils.startsWithIgnoreCase(entry.getKey(), CONNECTION_PREFIX))
.map(this::mapToConnection)
.collect(Collectors.toList());
return new Root(isAlive, connections);
}
private PosServerConnection mapToConnection(Map.Entry<String, Map<String, Object>> entry) {
Map<String, Object> connectionJsonMap = entry.getValue();
return objectMapper.convertValue(connectionJsonMap, Connection.class);
}
}
This way i can group all my Connections into one List in Root class.
My question is there any another way to do this ??
I'd like to do this without such big deserializer using just Jackson annotations on my Pojo classes
You can simply achieve this by using #JsonAnySetter annotation for customizing Setter for List<Connection> as follows. You can also reference to Jackson Annotation Examples to see how it works.
POJOs
public class Pojo {
private Root root;
//general getters, setters and toString
}
public class Root {
private String status;
private List<Connection> connections = new ArrayList<>();
public List<Connection> getConnections() {
return connections;
}
#JsonAnySetter
public void setConnections(String name, Connection connection) {
connection.setName(name);
this.connections.add(connection);
}
//other getters, setters and toString
}
public class Connection {
private String name;
private String status;
//general getters, setters and toString
}
Then you can serialize the given JSON string to Pojo with common way by Jackson:
Code Snippet
ObjectMapper mapper = new ObjectMapper();
Pojo pojo = mapper.readValue(jsonStr, Pojo.class);
System.out.println(pojo.getRoot().getConnections().toString());
Console output
[Connection [name=connection1, status=UP], Connection [name=connection2, status=UP]]

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: Keep references to keys in map values when deserializing

I have the following JSON with a map from user IDs to user details:
{
"users": {
"john": { "firstName": "John", "lastName": "Doe" },
"mark": { "firstName": "Mark", "lastName": "Smith" }
}
}
and I'm using the following code to deserialize the JSON into a Java objects:
class User {
public String userID;
public String firstName;
public String lastName;
}
public class Users {
public Map<String, User> users;
public static void main(String[] args) throws IOException {
ObjectMapper mapper = new ObjectMapper();
Reader source = Files.newBufferedReader(Paths.get("test.json"));
Users all = mapper.readValue(source, Users.class);
// ...
}
}
After the deserialization, I want the field User.userID to be set to the corresponding key in the users map.
For example all.users.get("john").userID should be "john".
How can I do that?
Create a custom deserializer for User object and use this for the Map. Here's a full example:
#Test
public void test() throws JsonParseException, JsonMappingException, IOException {
ObjectMapper mapper = new ObjectMapper();
Data data = mapper.readValue("{\"users\": {\"John\": {\"id\": 20}, \"Pete\": {\"id\": 30}}}", Data.class);
assertEquals(20, data.users.get("John").id);
assertEquals(30, data.users.get("Pete").id);
assertEquals("John", data.users.get("John").name);
assertEquals("Pete", data.users.get("Pete").name);
}
public static class Data {
#JsonDeserialize(contentUsing = Deser.class)
public Map<String, User> users;
}
public static class User {
public String name;
public int id;
}
public static class Deser extends JsonDeserializer<User> {
#Override
public User deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
String name = ctxt.getParser().getCurrentName();
User user = p.readValueAs(User.class);
user.name = name; // This copies the key name to the user object
return user;
}
}
The simplest solution for the problem is to implement a custom deserializer for the class in which you need the map key (see john16384's answer). This is however cumbersome if you have multiple maps with different value types in your JSON because you'd need one deserializer per type.
In this case, there is a better solution: I would create a custom #JsonMapKey annotation to mark the target properties for the map keys, and then register a generic custom deserializer that processes all occurrences of the annotation. These are the parts you need for this:
Custom #JsonMapKey annotation:
/**
* Annotation used to indicate that the annotated property shall be deserialized to the map key of
* the current object. Requires that the object is a deserialized map value.
*
* Note: This annotation is not a standard Jackson annotation. It will only work if this is
* explicitly enabled in the {#link ObjectMapper}.
*/
#Target({ ElementType.FIELD })
#Retention(RetentionPolicy.RUNTIME)
public #interface JsonMapKey {
}
Custom deserializer that processes the #JsonMapKey annotations:
public class JsonMapKeyDeserializer extends DelegatingDeserializer {
private static final long serialVersionUID = 1L;
private BeanDescription beanDescription;
public JsonMapKeyDeserializer(JsonDeserializer<?> delegate, BeanDescription beanDescription) {
super(delegate);
this.beanDescription = beanDescription;
}
#Override
protected JsonDeserializer<?> newDelegatingInstance(JsonDeserializer<?> newDelegatee) {
return new JsonMapKeyDeserializer(newDelegatee, beanDescription);
}
#Override
public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
String mapKey = p.getCurrentName();
Object deserializedObject = super.deserialize(p, ctxt);
// set map key on all fields annotated with #JsonMapKey
for (BeanPropertyDefinition beanProperty : beanDescription.findProperties()) {
AnnotatedField field = beanProperty.getField();
if (field != null && field.getAnnotation(JsonMapKey.class) != null) {
field.setValue(deserializedObject, mapKey);
}
}
return deserializedObject;
}
}
Registration of the custom deserializer in the ObjectMapper:
private static void registerJsonMapKeyAnnotation(ObjectMapper objectMapper) {
SimpleModule module = new SimpleModule();
module.setDeserializerModifier(new BeanDeserializerModifier() {
#Override
public JsonDeserializer<?> modifyDeserializer(DeserializationConfig config,
BeanDescription beanDescription, JsonDeserializer<?> originalDeserializer) {
return new JsonMapKeyDeserializer(originalDeserializer, beanDescription);
}
});
objectMapper.registerModule(module);
}
Then you only need to annotate the field to be used for the map key...
class User {
#JsonMapKey
public String userID;
public String firstName;
public String lastName;
}
... and deserialize your JSON with the prepared ObjectMapper:
Users all = registerJsonMapKeyAnnotation(new ObjectMapper()).readValue(source, Users.class);
First Create the ObjectMapper class object than configure it.
Try following one.
Sample Code
Map<K, V> map;
ObjectMapper mapper = new ObjectMapper();
mapper.configure(Feature.ALLOW_UNQUOTED_FIELD_NAMES, true);
map = mapper.readValue(jsonStr, new TypeReference<Map<K, V>>() {});
than you can get the value using Map.

Convert a part of Json to HashMap using Jackson ObjectMapper

I am trying to unmarshall a json file in a way such that few properties of Json are mapped into a HashMap that is present in my model class.Rest of the properties are mapped to the respective fields of the class.Please find the Json below:
{
"_id":2,
"Name":"xyz",
"Age":20,
"MEMO_TEXT":"yyy",
"MEMO_LINK":"zzz",
"MEMO_DOB":"",
"MEMO_USERNAME":"linie orange",
"MEMO_CATEGORY":2,
"MEMO_UID":"B82071415B07495F9DD02C152E4805EC"
}
And here is the Model class to which I want to map this Json:
public class Model{
private int _id;
private String name;
private int age
private HashMap<String, String> columns;
//Getters and Setter methods
}
So here, what i want is to get a map columns that contains keys "MEMO_TEXT","MEMO_LINK","MEMO_DOB","MEMO_USERNAME","MEMO_CATEGORY","MEMO_UID"
and rest of the properties in Json are mapped to their respective fields.
Is it possible to do this using ObjectMapper of Jackson Library?
You can use #JsonAnySetter to annotate a method to be called for "other" properties:
#Test
public void partial_binding() throws Exception {
Model model = mapper.readValue(Resources.getResource("partial_binding.json"), Model.class);
assertThat(model.name, equalTo("xyz"));
assertThat(model.columns, hasEntry("MEMO_TEXT", "yyy"));
assertThat(
mapper.writeValueAsString(model),
json(jsonObject()
.withProperty("Name", "xyz")
.withProperty("MEMO_TEXT", "yyy")
.withAnyOtherProperties()));
}
public static class Model {
#JsonProperty
private int _id;
#JsonProperty("Name")
private String name;
#JsonProperty("Age")
private int age;
private HashMap<String, String> columns;
#JsonAnyGetter
public HashMap<String, String> getColumns() {
return columns;
}
public void setColumns(HashMap<String, String> columns) {
this.columns = columns;
}
#JsonAnySetter
public void putColumn(String key, String value) {
if (columns == null) columns = new HashMap<>();
columns.put(key, value);
}
}
Also, #JsonAnyGetter does "kind of the reverse", so this should serialize and deserialize the same way.
One of several ways to achieve what you want is to add a constructor:
#JsonCreator
public Model(Map<String, Object> fields) {
this._id = (int) fields.remove("_id");
this.name = (String) fields.remove("Name");
this.age = (int) fields.remove("Age");
this.columns = new HashMap<String, String>();
for (Entry<String, Object> column : fields.entrySet()) {
columns.put(column.getKey(), column.getValue().toString());
}
}
Be aware that if you serialize it back to JSON the structure will be diffrent than the initial one.
Try using a SerializerProvider. A SerializerProvider can modify the deserialization, enabling custom deserialization.

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