Jackson Custom Serialization and Deserialization for Maps - java

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.

Related

How to convert JSON into Hashmap object

I am trying to automate PUT request for our rest service in which I am passing a PUT body. I am trying to use HashMap to create an object for the body and add the values to it.
I am not sure how to add values using Hashmap for the nested JSON elements.
My body is something like this:
{
"versions": [
{
"versionname": "Test",
"number": 1
}
],
"id": 19,
"name": "TEST",
}
My code is as below:
public static Map<String, Object> map = new HashMap<String, Object>();
map.put("id", "2");
map.put("name", "TEST");
My question is how to add values for 'versionname' and 'number' element into the map so that I can pass that in my PUT request's body? Any help much appreciated.
Use strict typing, do not use maps if you know your payload (typed from the top of my head, may not immediately compile):
public class Request {
public static class Version {
public String versionname;
public int number;
protected Version() {} // for deserializer
public Version(String versionname, int number) {
this.versionname = versionname;
this.number = number;
}
}
public List<Version> versions = new ArrayList<>();
public int id;
public String name;
}
Request request = new Request();
request.versions.add(new Version("Test", 1));
request.id = 19;
request.name = "TEST";
String jsonString = new ObjectMapper().writeValueAsString(request);
Request deserialized = new ObjectMapper().readValue(jsonString, Request.class);

Jackson JsonTypeInfo for contents of HashMap<String, Object>

I have a JSON file (that I have no control over) that looks like this:
{
"some-identifier": {
"#class": "some-prefix.ClassA",
"<classA-property1>": "value1",
"<classA-property2>": "value2",
},
"some-other-identifier": {
"#class": "some-other-prefix.ClassB",
"<classB-property1>": <... possibly nested objects ...>
},
<...>
}
(The classA-properties and classB-properties are the actual names of the members of ClassA and ClassB respectively.)
I would like to deserialize this into a HashMap (mapping each identifier to the actual object) and I want to use a custom TypeIdResolver to determine the actual class to be instantiated (which I can determine from the prefix and class name). The objects themselves should then be deserialized using the default deserializer.
After a lot of searching I couldn't make this work. I need some way to annotate the HashMap in order to set JsonTypeInfo and JsonTypeIdResolver for its content. All examples I've seen so far have those annotations on a base type that all subclasses extend from. However, in my case, there is no common parent class for the classes contained in the JSON (except Object of course). I thought about annotating Object itself with a mixin, but even then this would break default deserialization for the contained objects since it would then expect an #class property on all child objects.
Is there a solution for this scenario?
I think you can accomplish this by enabling the default type information for the object mapper like this:
new ObjectMapper().enableDefaultTyping(
ObjectMapper.DefaultTyping.JAVA_LANG_OBJECT,
JsonTypeInfo.As.PROPERTY);
Here is a complete example:
public class JacksonDefaultTypeInfo {
static class Bean1 {
public String value;
Bean1() {}
public Bean1(final String value) {
this.value = value;
}
#Override
public String toString() {
return "Bean1{" +
"value='" + value + '\'' +
'}';
}
}
static class Bean2 {
public int number;
Bean2() {}
Bean2(final int number) {
this.number = number;
}
}
public static void main(String[] args) throws IOException {
final Map<String, Object> map = new HashMap<>();
map.put("bean1", new Bean1("string"));
map.put("bean2", new Bean2(123));
final ObjectMapper objectMapper = new ObjectMapper()
.enableDefaultTyping(
ObjectMapper.DefaultTyping.JAVA_LANG_OBJECT,
JsonTypeInfo.As.PROPERTY);
final String json = objectMapper
.writerWithDefaultPrettyPrinter()
.writeValueAsString(map);
System.out.println(json);
final Map<String, Object> result = objectMapper.readValue(
json,
new TypeReference<Map<String, Object>>() {});
System.out.println(result);
}
}
Output:
{
"bean1" : {
"#class" : "stackoverflow.JacksonDefaultTypeInfo$Bean1",
"value" : "string"
},
"bean2" : {
"#class" : "stackoverflow.JacksonDefaultTypeInfo$Bean2",
"number" : 123
}
}
{bean1=Bean1{value='string'}, bean2=Bean2{number=123}}

De/Serialize Gson an array of arrays [duplicate]

I'm trying to parse some JSON data using gson in Java that has the following structure but by looking at examples online, I cannot find anything that does the job.
Would anyone be able to assist?
{
"data":{
"id":[
{
"stuff":{
},
"values":[
[
123,
456
],
[
123,
456
],
[
123,
456
],
],
"otherStuff":"blah"
}
]
}
}
You just need to create a Java class structure that represents the data in your JSON. In order to do that, I suggest you to copy your JSON into this online JSON Viewer and you'll see the structure of your JSON much clearer...
Basically you need these classes (pseudo-code):
class Response
Data data
class Data
List<ID> id
class ID
Stuff stuff
List<List<Integer>> values
String otherStuff
Note that attribute names in your classes must match the names of your JSON fields! You may add more attributes and classes according to your actual JSON structure... Also note that you need getters and setters for all your attributes!
Finally, you just need to parse the JSON into your Java class structure with:
Gson gson = new Gson();
Response response = gson.fromJson(yourJsonString, Response.class);
And that's it! Now you can access all your data within the response object using the getters and setters...
For example, in order to access the first value 456, you'll need to do:
int value = response.getData().getId().get(0).getValues().get(0).get(1);
Depending on what you are trying to do. You could just setup a POJO heirarchy that matches your json as seen here (Preferred method). Or, you could provide a custom deserializer. I only dealt with the id data as I assumed it was the tricky implementation in question. Just step through the json using the gson types, and build up the data you are trying to represent. The Data and Id classes are just pojos composed of and reflecting the properties in the original json string.
public class MyDeserializer implements JsonDeserializer<Data>
{
#Override
public Data deserialize(JsonElement je, Type type, JsonDeserializationContext jdc) throws JsonParseException
{
final Gson gson = new Gson();
final JsonObject obj = je.getAsJsonObject(); //our original full json string
final JsonElement dataElement = obj.get("data");
final JsonElement idElement = dataElement.getAsJsonObject().get("id");
final JsonArray idArray = idElement.getAsJsonArray();
final List<Id> parsedData = new ArrayList<>();
for (Object object : idArray)
{
final JsonObject jsonObject = (JsonObject) object;
//can pass this into constructor of Id or through a setter
final JsonObject stuff = jsonObject.get("stuff").getAsJsonObject();
final JsonArray valuesArray = jsonObject.getAsJsonArray("values");
final Id id = new Id();
for (Object value : valuesArray)
{
final JsonArray nestedArray = (JsonArray)value;
final Integer[] nest = gson.fromJson(nestedArray, Integer[].class);
id.addNestedValues(nest);
}
parsedData.add(id);
}
return new Data(parsedData);
}
}
Test:
#Test
public void testMethod1()
{
final String values = "[[123, 456], [987, 654]]";
final String id = "[ {stuff: { }, values: " + values + ", otherstuff: 'stuff2' }]";
final String jsonString = "{data: {id:" + id + "}}";
System.out.println(jsonString);
final Gson gson = new GsonBuilder().registerTypeAdapter(Data.class, new MyDeserializer()).create();
System.out.println(gson.fromJson(jsonString, Data.class));
}
Result:
Data{ids=[Id {nestedList=[[123, 456], [987, 654]]}]}
POJO:
public class Data
{
private List<Id> ids;
public Data(List<Id> ids)
{
this.ids = ids;
}
#Override
public String toString()
{
return "Data{" + "ids=" + ids + '}';
}
}
public class Id
{
private List<Integer[]> nestedList;
public Id()
{
nestedList = new ArrayList<>();
}
public void addNestedValues(final Integer[] values)
{
nestedList.add(values);
}
#Override
public String toString()
{
final List<String> formattedOutput = new ArrayList();
for (Integer[] integers : nestedList)
{
formattedOutput.add(Arrays.asList(integers).toString());
}
return "Id {" + "nestedList=" + formattedOutput + '}';
}
}

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

Deserializing a JSON object using gson when keys are now known in advance

I am trying to parse a JSON object which consists of an Array of Customer objects. Each customer object contains a number of key/value pairs:
{
"Customers":
[
{
"customer.name": "acme corp",
"some_key": "value",
"other_key": "other_value",
"another_key": "another value"
},
{
"customer.name": "bluechip",
"different_key": "value",
"foo_key": "other_value",
"baa": "another value"
}
]
}
The complication is that the keys are not known to me in advance. A second complication is that the keys contain periods (.) that mean that even when I have tried to map them to a field, it fails.
I have been trying to map these to a Customers class:
Customers data = new Gson().fromJson(responseStr, Customers.class);
which looks like this:
public class Customers {
public List<Customer> Customers;
static public class Customer {
public List<KeyValuePair> properties;
public class KeyValuePair {
String key;
Object value;
}
}
}
My problem is that in when I load this class from the JSON, my Customers list populates, but their properties are null. How can I make GSON deal with the fact that I don't know the key names?
I have tried various other approaches including putting a HashMap in the Customer class, in place of the KeyValuePair class.
A different approach is that, you can create a Map of key values from the JSON and then look for the values, since the keys are not known
Gson gson = new Gson();
Type mapType = new TypeToken<Map<String,List<Map<String, String>>>>() {}.getType();
Map<String,List<Map<String, String>> >map = gson.fromJson(responseStr, mapType);
System.out.println(map);
Customers c = new Customers();
c.setCustomers(map.get("Customers"));
System.out.println(c.getCustomers());
Modify your Customers class like this
public class Customers {
public List<Map<String, String>> customers;
public List<Map<String, String>> getCustomers() {
return customers;
}
public void setCustomers(List<Map<String, String>> customers) {
this.customers = customers;
}
}

Categories