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

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");
}

Related

How to convert a json string to an object

here is my pojo
public class Data{
List<Object> objects;
String owneruid;
}
if the out put is pure json like this
{"object":[{"p1":100,"p2":"name","p3":"sfa0","p4":300}],"owneruid":"owneruid"}
then iam able to convert with no worries but
here is my output
{
"object":"[{\"p1\":32,\"p3\":470,\"p3\":\"213\",\"p4\":\"name\"}]",
"owneruid":"6697729776330393738"
}
im converting a json string to string because to store in my db as it does not accept json so when i query returns like above so every time i need to fetch the value and convert it to json object and put it in list and display. can you suggest me a better approach.
And when i try to convert a list of custom classes to json using GSON
ArrayList<Object> list=new ArrayList<>();
Object object=new Object();
object.setP1(3);
object.setP2(4);
list.add(object);
Gson gson=new Gson();
String json = gson.toJson(list);
Required:
{"object":[{"p1":100,"p2":"name","p2":"sfa0","p4":300}],"owneruid":"owneruid"}
buts it ends like this
{"object":"[{\"p1\":313,\"p2\":470,\"p3\":\"1521739327417\",\"p4\":\"name\"}]","owneruid":"6697729776330393738"}
You have to use any json frameworks. E.g. Jackson or Gson. As alternative you could do smth. like this. Just evaluate JavaScript.
public static void main(String... args) throws ScriptException {
ScriptEngine js = new ScriptEngineManager().getEngineByName("javascript");
Object obj = js.eval("[{\"width\":313,\"height\":470,\"mediauid\":\"1521739327417\",\"mediatype\":\"image\"}]");
// res is either List<Object> or Map<String, Object>
Object res = convertIntoJavaObject(obj);
}
private static Object convertIntoJavaObject(Object obj) {
if (!(obj instanceof ScriptObjectMirror))
return obj;
ScriptObjectMirror mirror = (ScriptObjectMirror)obj;
if (mirror.isArray())
return mirror.entrySet().stream()
.map(entry -> convertIntoJavaObject(entry.getValue()))
.collect(Collectors.toList());
Map<String, Object> map = new HashMap<>();
mirror.entrySet().forEach((key, value) -> map.put(key, convertIntoJavaObject(value)));
return map;
}
You can use the below code snippet as it seems fit for your case.
ObjectMapper can be found with Jackson framework. inputJson is the JSON string you have mentioned.
ObjectMapper mapper = new ObjectMapper();
Object mediaMetaDataObj = mapper.readValue( inputJson, Object.class );
Hope this helps.

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;
}

Deserializing into a HashMap of custom objects with jackson

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!"

Why cannot Google JSON library properly serialize this HashMap?

I have two HashMaps to be serialised to JSON using Google Gson library:
final Map<String, String> map1 = new HashMap<String, String>() {
{
put("abc", "def");
}
};
final Map<String, String> map2 = new HashMap<String, String>();
map2.put("abc", "def");
final Gson gson = new Gson();
final String s1 = gson.toJson(map1); // "null"
final String s2 = gson.toJson(map2); // {"abc":"def"}
Why is the second HashMap correctly serialized but not the first HashMap?
Gson uses reflection and doesn't care whether or not a class implements Serializable. Gson does, however, need a no-arg constructor (see the design doc):
Gson needs to create a dummy class instance before it can deserialize
Json data into its fields ... we create class instances by invoking the
parameterless constructor ...
When you call toJson(Object obj) Gson will use obj.getClass() to figure out how to construct a "dummy instance" of obj which in your case is an anonymous inner class. Inner classes require a reference to their containing class in order to be constructed. The reference to the outer class is not available at the time of serialization which is why your result is null. You can get around this issue by providing Gson with a little more information about how to construct your object:
final Map<String, String> map1 = new HashMap<String, String>() {
{
put("abc", "def");
}
};
Gson gson = new Gson();
String json = gson.toJson(map1, new TypeToken<Map<String, String>>() {}.getType()); // {"abc":"def"}
Edit: Note that this only works because your anonymous class can be cast to Map<String, String>. If you had a more complex inner class such as:
final Map<String, String> map1 = new HashMap<String, String>() {
private String additionalData = "Foo";
{
put("abc", "def");
}
};
additionalData would not be in the output.

Categories