Deserializing into a HashMap of custom objects with jackson - java

I have the following class:
import org.codehaus.jackson.annotate.JsonIgnoreProperties;
import org.codehaus.jackson.annotate.JsonProperty;
import java.io.Serializable;
import java.util.HashMap;
#JsonIgnoreProperties(ignoreUnknown = true)
public class Theme implements Serializable {
#JsonProperty
private String themeName;
#JsonProperty
private boolean customized;
#JsonProperty
private HashMap<String, String> descriptor;
//...getters and setters for the above properties
}
When I execute the following code:
HashMap<String, Theme> test = new HashMap<String, Theme>();
Theme t1 = new Theme();
t1.setCustomized(false);
t1.setThemeName("theme1");
test.put("theme1", t1);
Theme t2 = new Theme();
t2.setCustomized(true);
t2.setThemeName("theme2");
t2.setDescriptor(new HashMap<String, String>());
t2.getDescriptor().put("foo", "one");
t2.getDescriptor().put("bar", "two");
test.put("theme2", t2);
String json = "";
ObjectMapper mapper = objectMapperFactory.createObjectMapper();
try {
json = mapper.writeValueAsString(test);
} catch (IOException e) {
e.printStackTrace();
}
The json string produced looks like this:
{
"theme2": {
"themeName": "theme2",
"customized": true,
"descriptor": {
"foo": "one",
"bar": "two"
}
},
"theme1": {
"themeName": "theme1",
"customized": false,
"descriptor": null
}
}
My problem is getting the above json string to de-serizlize back into a
HashMap<String, Theme>
object.
My de-serialization code looks like this:
HashMap<String, Themes> themes =
objectMapperFactory.createObjectMapper().readValue(json, HashMap.class);
Which de-serializes into a HashMap with the correct keys, but does not create Theme objects for the values. I don't know what to specify instead of "HashMap.class" in the readValue() method.
Any help would be appreciated.

You should create specific Map type and provide it into deserialization process:
TypeFactory typeFactory = mapper.getTypeFactory();
MapType mapType = typeFactory.constructMapType(HashMap.class, String.class, Theme.class);
HashMap<String, Theme> map = mapper.readValue(json, mapType);

You can use TypeReference class which does the type casting for map with user defined types. More documentation at https://github.com/FasterXML/jackson-databind/
ObjectMapper mapper = new ObjectMapper();
Map<String,Theme> result =
mapper.readValue(src, new TypeReference<Map<String,Theme>>() {});

You can make a POJO that extends a Map.
This is important for dealing with nested maps of objects.
{
key1: { nestedKey1: { value: 'You did it!' } }
}
This can be deserialized via:
class Parent extends HashMap<String, Child> {}
class Child extends HashMap<String, MyCoolPojo> {}
class MyCoolPojo { public String value; }
Parent parent = new ObjectMapper().readValue(json, Parent.class);
parent.get("key1").get("nestedKey1").value; // "You did it!"

Related

Jackson Custom Serialization and Deserialization for Maps

I faced a situation that could not be solved. The problem is that i serialized a Map<String, Object> using jackson library and it works well then when i deserialize the json value, it cannot find original class. Here is the problematic Scenario.
public class Message {
private Map<String, Object> myMap = new HashMap<>();
//getter
}
public class Address {
private String postalCode;
private String flatNo;
//getter-setters-constructors
}
Here is the main
public class Main {
public static void main(String[] args) {
ObjectMapper mapper = new ObjectMapper();
Message message = new Message();
message.getMyMap().put("address", new Address("123123", "30"));
message.getMyMap().put("ID", 999);
message.getMyMap().put("anyKey", "anyOtherValue");
String json = mapper.writeValueAsString(message);
System.out.println("json:" + json);
Message message2 = mapper.readValue(json, Message.class);
message2.getMyMap().get("address"); // this comes LinkedHashMap. I want it to be Address.class
}
}
Output:
{
"myMap" : {
"address" : {
"postalCode" : "123123",
"flatNo" : "30"
},
"ID" : 999,
"anyKey" : "anyOtherValue"
}
}
When i check message2 object, it includes myMap map and it has "address" key. However, the value of key("address") is not Address.class type. its type is LinkedHashMap.class. How can i configure the jackson to use Address.class when the key comes "address"?
Thanks in advance.

How can I transform a Map with a value of Object to the appropriate type?

I am working on a project where I need to accept a Map called properties that is of type Map<String, Object>. There are going to be potentially many different keys in this Map, but I only care about one: xpath. An xpath can have one of three different types of values:
A string, such as {"xpath": "path/to/xml/tag"}
A List of xpaths, such as: {"xpath": ["path/to/xml/tag1", "tag2", "path/tag3"}
A Map<String, Map<String, Boolean>>, such as:
{
"xpath":
{
"path/to/xml":
{
"setting1?": true,
"setting2?": true
},
"path/tag2":
{
"setting1?": false,
"setting2": true
},
"path/to/tag3": null
}
}
Now I have three variables: String xpath, Set<String> xpaths, Map<String, Map<String, boolean> xpathMap. I have a function that is supposed to try and map the values of the "xpath" key in the properties map, and it looks like this:
private void decideXPathType(Map<String, Object> properties)
{
Object propertiesXPath = properties.get("xpath");
if (propertiesXPath instanceof String)
{
this.xpath = (String) propertiesXPath;
} else if (propertiesXPath instanceof List)
{
this.xpaths = new HashSet<String>((List) propertiesXPath);
} else if (propertiesXPath instanceof Map)
{
for (Object key : ((Map) propertiesXPath).keySet())
{
Map<String, Boolean> value = (Map<String, Boolean>) ((Map) propertiesXPath).get(key);
this.xpathMap.put((String) key, value);
}
} else
{
throw new IllegalArgumentException("the xpath value is neither String, List, or Map<String, Boolean>");
}
}
But this function looks so bad - there is so much casting, etc - and although it works, it just looks too messy, and I imagine something can go wrong... any ideas on how I can make this cleaner?
Edit: Some more details
The properties map is originally a json JsonNode requestBody that I receive from a service. Using ObjectMapper, I create a properties map as such:
Map<String, Object> properties = new ObjectMapper().convertValue(new ObjectMapper().readTree(requestBody), new TypeReference<Map<String, Object>>(){});
If I receive a json string that is the value of the xpathMap example that I gave, I get something that looks like this:
Hope this information helps?
In your JSON, use different keys for these different types of values: String, List and Map. Deserializing a map:
#Test
public void test0() throws IOException {
ObjectMapper om = new ObjectMapper();
InputStream inputStream = getClass().getClassLoader().getResourceAsStream("xpath-object.json");
JsonNode jsonNode = om.readTree(inputStream);
Map<String, Map<String, Boolean>> value = om.readValue(jsonNode.get("xpath").toString(), Map.class);
// prints {path/to/xml={setting1?=true, setting2?=true}, path/to/tag3=null, path/tag2={setting1?=false, setting2=true}}
System.out.println(value);
}
If you need to work with 3rd party JSON, you can use following approach:
#Test
public void test() throws IOException {
testThemAll("xpath-scalar.json");
testThemAll("xpath-array.json");
testThemAll("xpath-object.json");
// prints:
// path/to/xml/tag
// [path/to/xml/tag1, tag2, path/tag3]
// {path/to/xml={setting1?=true, setting2?=true}, path/to/tag3=null, path/tag2={setting1?=false, setting2=true}}
}
private void testThemAll(String fileName) throws IOException {
ObjectMapper om = new ObjectMapper();
InputStream inputStream = getClass().getClassLoader().getResourceAsStream(fileName);
JsonNode jsonNode = om.readTree(inputStream).get("xpath");
if (jsonNode.isValueNode())
System.out.println(jsonNode.asText());
else if (jsonNode.isArray()) {
System.out.println(om.readValue(jsonNode.toString(), List.class));
} else if (jsonNode.isObject()) {
Map<String, Map<String, Boolean>> value = om.readValue(jsonNode.toString(), Map.class);
System.out.println(value);
}
}

Convert a JSON object to a Map object

I have a JSON object which look as the following:
[{"var1":"value1","var2":"value2"},{"var2":"value22","var3":[["0","1","2"],["3","4","5"],["6","7","8"]]}]
(Note: var2 appears twice in the example and the complex form of the value of var3.)
The desired output should be a map object like:
key value
var1 value1
var2 value2,value22
var3 [["0","1","2"],["3","4","5"],["6","7","8"]]
What I would like is to convert this to a map object with the first elements (var1, var2, var3) as keys and the corresponding values as the values in the map. In case with the identical keys (e.g.: var2) the two values which belong to this key shoul be concatenated, but separated, e.g., by a comma.
Can someone help me with this?
You don't need an adapter to parse a json. you just need to tell ObjectMapper exactly what type to parse into. you also need a bit of post processing since you want some special processing regarding duplicate keys
you get Jackson from GIT: https://github.com/FasterXML/jackson
here is a complete solution for you:
import java.util.*;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.type.TypeFactory;
public class Test
{
public static void main(String[] args)
{
String input = "[{\"var1\":\"value1\",\"var2\":\"value2\"},{\"var2\":\"value22\",\"var3\":[[\"0\",\"1\",\"2\"],[\"3\",\"4\",\"5\"],[\"6\",\"7\",\"8\"]]}]" ;
Map<String, String> result = new HashMap<>(); // final result, with duplicate keys handles and everything
try {
// ObjectMapper is Jackson json parser
ObjectMapper om = new ObjectMapper();
// we need to tell ObjectMapper what type to parse into
// in this case: list of maps where key is string and value is some cimplex Object
TypeFactory tf = om.getTypeFactory();
JavaType mapType = tf.constructMapType(HashMap.class, String.class, Object.class);
JavaType listType = tf.constructCollectionType(List.class, mapType);
#SuppressWarnings("unchecked")
// finally we parse the input into the data struct
List<Map<String, Object>> list = (List<Map<String, Object>>)om.readValue(input, listType);
// post procesing: populate result, taking care of duplicates
for (Map<String, Object> listItem : list) {
for (Map.Entry<String, Object> mapItem : listItem.entrySet()) {
String key = mapItem.getKey();
String value = mapItem.getValue().toString();
if (result.containsKey(key)) value = result.get(key) + "," + value;
result.put(key, value);
}
}
// result sohuld hold expected outut now
System.out.println(result);
} catch (Exception e) {
e.printStackTrace();
}
}
}
output:
{var3=[[0, 1, 2], [3, 4, 5], [6, 7, 8]], var2=value2,value22, var1=value1}
You can use Jackson to convert to and from JSON to Map. Use the following code and instantiate the JSonAdapter class, use the method marshal(String) to convert the json string to map and unmarshall(Map) for vice versa.
import java.io.IOException;
import java.util.Map;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
public class JsonAdapter {
private static final ObjectMapper MAPPER = new ObjectMapper();
public String unmarshal(final Map<?, ?> jsonList) throws Exception {
return MAPPER.writeValueAsString(jsonList);
}
public Map<?, ?> marshal(final String jsonString) throws Exception {
try {
return MAPPER.readValue(jsonString, new TypeReference<Map<?, ?>>() {
});
} catch (final IOException e) {
e.printStackTrace();
}
return null;
}
}

How to convert Java class to Map<String, String> and convert non-string members to json using jackson?

I have some class in Java that I want to convert to a Map<String, String>. The catch is that any fields of my java class that don't have an obvious String representation (collections, other classes) should be converted to json strings.
Here's an example:
#Data
#AllArgsConstructor
class MyClass {
String field1;
Long field2;
Set<String> field3;
OtherClass field4;
}
#Data
#AllArgsConstructor
class OtherClass {
String field1;
String field2;
}
ObjectMapper mapper = new ObjectMapper();
MyClass myClass = new MyClass("value",
123L,
Sets.newHashSet("item1", "item2"),
new OtherClass("value1", "value2"));
Map<String, String> converted =
mapper.convertValue(myClass, new TypeReference<Map<String, String>>(){});
At this point, converted should look like the following:
"field1" -> "value"
"field2" -> "123"
"field3" -> "[\"item1\", \"item2\"]"
"field4" -> "{\"field1\":\"value1\",\"field2\":\"value2\"}"
Instead, the call to mapper.convertValue fails when trying to deserizlize the Set with the exception java.lang.IllegalArgumentException: Can not deserialize instance of java.lang.String out of START_ARRAY token.
Are there any special configurations I can annotate MyClass with or ways to configure the ObjectMapper to make this work the way I want it to?
Here's one way to do it.
private static final ObjectMapper mapper = new ObjectMapper();
public Map<String, String> toMap(Object obj) {
// Convert the object to an intermediate form (map of strings to JSON nodes)
Map<String, JsonNode> intermediateMap = mapper.convertValue(obj, new TypeReference<Map<String, JsonNode>>() {});
// Convert the json nodes to strings
Map<String, String> finalMap = new HashMap<>(intermediateMap.size() + 1); // Start out big enough to prevent resizing
for (Map.Entry<String, JsonNode> e : intermediateMap.entrySet()) {
String key = e.getKey();
JsonNode val = e.getValue();
// Get the text value of textual nodes, and convert non-textual nodes to JSON strings
String stringVal = val.isTextual() ? val.textValue() : val.toString();
finalMap.put(key, stringVal);
}
return finalMap;
}
And if you want to convert the Map<String, String> back to the original class...
public static <T> T fromMap(Map<String, String> map, Class<T> clazz) throws IOException {
// Convert the data to a map of strings to JSON nodes
Map<String, JsonNode> intermediateMap = new HashMap<>(map.size() + 1); // Start out big enough to prevent resizing
for (Map.Entry<String, String> e : map.entrySet()) {
String key = e.getKey();
String val = e.getValue();
// Convert the value to the right type of JsonNode
JsonNode jsonVal;
if (val.startsWith("{") || val.startsWith("[") || "null".equals(val)) {
jsonVal = mapper.readValue(val, JsonNode.class);
} else {
jsonVal = mapper.convertValue(val, JsonNode.class);
}
intermediateMap.put(key, jsonVal);
}
// Convert the intermediate map to an object
T result = mapper.convertValue(intermediateMap, clazz);
return result;
}

How to deserialize a Map<String, Object> into POJO?

I have a Map<String, Object> which contains a deserialized form of JSON. I would like to deserialize this into the fields of a POJO.
I can perform this using Gson by serializing the Map into a JSON string and then deserializing the JSON string into the POJO, but this is inefficient (see example below). How can I perform this without the middle step?
The solution should preferably use either Gson or Jackson, as they're already in use by the project.
Example code:
import java.util.HashMap;
import java.util.Map;
import com.google.gson.Gson;
public class Test {
public static void main(String[] args) {
Map<String, Object> innermap = new HashMap<String, Object>();
innermap.put("number", 234);
innermap.put("string", "bar");
Map<String, Object> map = new HashMap<String, Object>();
map.put("number", 123);
map.put("string", "foo");
map.put("pojo2", innermap);
Gson gson = new Gson();
// How to perform this without JSON serialization?
String json = gson.toJson(map);
MyPojo pojo = gson.fromJson(json, MyPojo.class);
System.out.println(pojo);
}
}
class MyPojo {
private int number;
private String string;
private MyPojo pojo2;
#Override
public String toString() {
return "MyPojo[number=" + number + ", string=" + string + ", pojo2=" + pojo2 + "]";
}
}
Using Gson, you can turn your Map into a JsonElement, which you can then parse in exactly the same way using fromJson:
JsonElement jsonElement = gson.toJsonTree(map);
MyPojo pojo = gson.fromJson(jsonElement, MyPojo.class);
This is similar to the toJson method that you are already using, except that it avoids writing the JSON String representation and then parsing that JSON String back into a JsonElement before converting it to your class.
In Jackson you can use convertValue method. See below example:
mapper.convertValue(map, MyPojo.class);
You can use jackson library for convert map object to direct POJO.
Map<String, String> map = new HashMap<>();
map.put("id", "5");
map.put("name", "Bob");
map.put("age", "23");
map.put("savings", "2500.39");
ObjectMapper mapper = new ObjectMapper();
MyPojo pojo = mapper.convertValue(map, MyPojo.class);
You can directly construct MyPojo by giving it the map.
Something like
MyPojo pojo = new MyPojo(map);
And declaring an according constructor :
public MyPojo(Map<String, Object> map){
this.number=map.get("number");
this.string=map.get("string");
this.pojo2=new MyPojo(); // I don't know if this is possible
this.pojo2.string=map.get("pojo2").get("string");
this.pojo2.number=map.get("pojo2").get("number");
}

Categories