How to serialize/deserialize object to Map - java

I have one specific case. I need to serialize/deserialize an object to Map<String, Object>. I have a class that looks like the following:
public class Data {
public String name;
public Map<String, Object> options = new HashMap<>();
}
I can put to this options objects of any type. For instance:
public class Option {
public int id;
...
}
public class TestOpt {
public name;
...
}
and I try to serialize and deserialize it:
public static void main(String... args) {
ObjectMapper mapper = new ObjectMapper();
Option o = new Option();
o.id = 1;
TestOpt t = new TestOpt();
t.name = "fff";
Data data = new Data();
data.name = "data";
data.options.put("o", o);
data.options.put("t", t);
String json = mapper.writeValueAsString(data);
Data d1 = mapper.readValue(json, Data.class);
// I get error because options.get("o") contains LinkedHashMap instead of Option.class
System.out.println(((Option)d1.options.get("o")).id);
}
How can I fix this issue?

The value of the serialized json is
{"name":"data","options":{"t":{"name":"fff"},"o":{"id":1}}}
So, the problem is that the object mapper has no way to tell that the o value inside the json is an Option. The best guess is that it could be a map and thus it is deserialized as a LinkedHashMap.
If you are sure that the element o is an Option, you can convert the value using an object mapper:
Option option = mapper.convertValue(d1.options.get("o"), Option.class);
But please note, that this means that the value is again serialized and then deserialized using the right type information. You can do that, but it is not a good solution.
If it is possible, a better way would be to change your model from a generic map to a specific class that contains the type information:
class Data {
public String name;
public DataOptions options = new DataOptions();
}
class DataOptions {
public Option o;
public TestOpt t;
}
Serializing this model has the same json representation as the model using a map, and the model can be used to deserialize the json from your example.

Related

How do I serialize a Dictionary items as flat properties of the class?

I have a class
public class Car {
public String color = null;
public String brand = null;
public HashMap<String,String> attributes;
public Car() {
this.attributes = new HashMap<>();
}
public static void main( String[] args ) {
Car car = new Car();
car.color = "Red";
car.brand = "Hyundai";
car.attributes.put("key1", "value1");
car.attributes.put("key2", "value1");
Gson gson = new Gson();
String json = gson.toJson(car);
System.out.println(json);
}
}
This currently serializes my car object into
{"color":"Red","brand":"Hyundai","attributes":{"key1":"value1","key2":"value1"}}
But I would like to unpack and serialize the attributes Dictionary from Car class as individual properties rather dictionary.
Ideally, i would like my json to be,
{"color":"Red","brand":"Hyundai","key1":"value1","key2":"value1"}
How do I achieve the same in GSON?
As explained in this Thread, there is no easy or straight forward way to achieve this.
If there is a scope of using Jackson, it can be achieved using #JsonUnwrapped but it doesn't work with Maps as explained here
but there is work around.
#JsonIgnore
public HashMap<String,String> attributes;
#JsonAnyGetter
public HashMap<String, String> getAttributes() {
return attributes;
}
And this will help create the required JSON.
(new ObjectMapper()).writeValueAsString(car)
Note. This is an alternate approach.
Use #JsonAnyGetteron the getter of your attributes

Can I deserialise a JSON string into an existing object?

I'm using the GSON library to work with data from the Tone Analyzer API (IBM Bluemix)
In my application, I create a ToneAnalysis object using a static method as I only need to read the object properties and never create a new instance of it. So I will never need to do this:
ToneAnalysis ta = new ToneAnalysis();
The way I'm doing things at the moment are::
string json = "{\"document_tone\": { ... } }";
ToneAnalysis ta = ToneAnalysis.fromJsonString(json)
This approach means I have ended up with a private parameter-less empty constructor:
public class ToneAnalysis {
private DocumentTone document_tone;
public DocumentTone getDocumentTone() {
return this.document_tone;
}
public static ToneAnalysis fromJsonString(String json) {
return new Gson().fromJson(json, ToneAnalysis.class);
}
private ToneAnalysis() {
}
}
Because fromJson creates the object via reflection. I am unable to do this:
this = gson.fromJson(json, ToneAnalysis.class);
Is there any way to allow a JSON object to be deserialised into an existing object or do I need to rethink my design?
do I need to rethink my design?
Not really, because ToneAnalysis has no non-static final fields. When an object has no final fields, then you can deserialize JSON into that object like this:
public class Foo {
Object foo, bar, baz, qux, foobar, barfoo;
public void deserializeJsonIntoThis(String json) {
Foo deserialized = new Gson().fromJson(json, Foo.class);
this.foo = deserialized.foo;
this.bar = deserialized.bar;
this.baz = deserialized.baz;
// ... copy other fields from deserialized to this like the above
}
}
In your case, the only field you have to copy is document_tone. That means you can deserialize ToneAnalysis instances' JSONs into existing ToneAnalysis instances with a one-liner!
public void deserializeJsonIntoThis(String json) {
this.document_tone = fromJsonString(json).document_tone;
}

JSON HashMap deserialization

I want to deserialize this JSON "{\"m\":{\"Test\":{\"nombre\":\"jose\",\"apellidos\":\"jose\",\"edad\":30}}}" in to PersonaContainer.
public class Persona {
private String nombre;
private String apellidos;
private int edad;
... getters and setters;
}
public class PersonaContainer {
private Map m = new HashMap<String,Persona>();
public Map getM() {
return m;
}
public void setM(Map m) {
this.m = m;
}
}
Then I, create an Object of persona and put it inside persona container with the next code
public class MyJSONTest {
public static void main(String args[]) {
ObjectMapper mapper = new ObjectMapper();
Map m = new HashMap<String,persona>();
Persona p = new Persona();
p.setNombre("jose");
p.setApellidos("jose");
p.setEdad(30);
m.put("Test", p);
PersonaContainer per = new PersonaContainer();
per.setM(m);
//convert Map to json string
try {
System.out.println(mapper.writeValueAsString(per));
} catch (IOException e) {
e.printStackTrace();
}
// convert json to Map
String json = "{\"m\":{\"Test\":{\"nombre\":\"jose\",\"apellidos\":\"jose\",\"edad\":30}}}";
try {
PersonaContainer pers = mapper.readValue(json, PersonaContainer.class);
Persona per1 = (Persona) pers.getM().get("Test");
System.out.println(per1.getNombre());
} catch (Exception e) {
e.printStackTrace();
}
}
}
After the serialization, I use ObjectMapper to get deserialize JSON in to a PersonaContainer Object, but when I try to obtain "Test" from HashMap "m" and cast it to Person Object i get this error:
java.lang.ClassCastException: java.util.LinkedHashMap cannot be cast to persona
at MyJSONTest.main(MyJSONTest.java:52)
any advice please?
Given only
private Map m = ...;
Jackson has no idea what types you expect for the Map's keys and values. It therefore uses its defaults. For regular objects, it uses LinkedHashMap. In other words, it will deserialize
{"nombre":"jose","apellidos":"jose","edad":30}
into a LinkedHashMap instance. When you try to use it as a Persona, it fails with the ClassCastException.
Instead, don't use raw types. Declare the Map with proper generic arguments.
private Map<String, Persona> m = ...;
Note that Spring will deserialize the corresponding JSON (m) into a LinkedHashMap, not a HashMap. If you want a HashMap, declare the field with the type HashMap.

how can I deserialize a non unified json in Java?

I want to send the server an http request with this json (upper line)
and I want to get such a json and parse it to Java object (lower line)
I remember from last times, that a missing field in a collection that I want to deserialize
crashes the deserialization
(for a single deserialization, if the json has no such field - a default value is inserted)
Is there any way I can create a single Java class to represent both the request json and the two types on response json objects?
My try:
public class ConfigValue {
public String key;
public String defaultValue;
public String value;
}
Gson gson = new Gson();
Type collectionType = new TypeToken<Array<ConfigValue>>() {
}.getType();
ConfigValue[] configValues = (ConfigValue[]) gson
.fromJson(result, collectionType);
Neither of the two JSON strings in your image are directly a list (or array) of ConfigValue objects. They are in fact a JSON object, with one property configValues, which is a list of ConfigValue objects. You therefore need a wrapper class to deserialize them to:
public class ConfigValues {
public ConfigValue[] configValues;
}
public class ConfigValue {
public String key;
public String defaultValue;
public String value;
}
public static void main(String[] args) {
String firstJson = "{\"configValues\":[{\"key\":\"radiusMeters\",\"value\":\"200\"}]}";
String secondJson = "{\"configValues\":[{\"key\":\"redeemExpirationMins\",\"defaultValue\":\"300\"},{\"key\":\"radiusMeters\",\"value\":\"200\",\"defaultValue\":\"400\"}]}";
Gson gson = new Gson();
ConfigValues firstConfigValues = gson.fromJson(firstJson, ConfigValues.class);
ConfigValues secondConfigValues = gson.fromJson(secondJson, ConfigValues.class);
System.out.println(firstConfigValues);
System.out.println(secondConfigValues);
}
If you add toString methods to the two classes, the main method prints the following deserialized objects:
ConfigValues(configValues=[ConfigValue(key=radiusMeters, defaultValue=null, value=200)])
ConfigValues(configValues=[ConfigValue(key=redeemExpirationMins, defaultValue=300, value=null), ConfigValue(key=radiusMeters, defaultValue=400, value=200)])
You can see that any missing fields of ConfigValue are deserialized to null.

How to keep fields sequence in Gson serialization

Seems like Gson.toJson(Object object) generates JSON code with randomly spread fields of the object. Is there way to fix fields order somehow?
public class Foo {
public String bar;
public String baz;
public Foo( String bar, String baz ) {
this.bar = bar;
this.baz = baz;
}
}
Gson gson = new Gson();
String jsonRequest = gson.toJson(new Foo("bar","baz"));
The string jsonRequest can be:
{ "bar":"bar", "baz":"baz" } (correct)
{ "baz":"baz", "bar":"bar" } (wrong sequence)
You'd need to create a custom JSON serializer.
E.g.
public class FooJsonSerializer implements JsonSerializer<Foo> {
#Override
public JsonElement serialize(Foo foo, Type type, JsonSerializationContext context) {
JsonObject object = new JsonObject();
object.add("bar", context.serialize(foo.getBar());
object.add("baz", context.serialize(foo.getBaz());
// ...
return object;
}
}
and use it as follows:
Gson gson = new GsonBuilder().registerTypeAdapter(Foo.class, new FooJsonSerializer()).create();
String json = gson.toJson(foo);
// ...
This maintains the order as you've specified in the serializer.
See also:
Gson User Guide - Custom serializers and deserializers
If GSON doesn't support definition of field order, there are other libraries that do. Jackson allows definining this with #JsonPropertyOrder, for example. Having to specify one's own custom serializer seems like awful lot of work to me.
And yes, I agree in that as per JSON specification, application should not expect specific ordering of fields.
Actually Gson.toJson(Object object) doesn't generate fields in random order. The order of resulted json depends on literal sequence of the fields' names.
I had the same problem and it was solved by literal order of properties' names in the class.
The example in the question will always return the following jsonRequest:
{ "bar":"bar", "baz":"baz" }
In order to have a specific order you should modify fields' names, ex: if you want baz to be first in order then comes bar:
public class Foo {
public String f1_baz;
public String f2_bar;
public Foo ( String f1_baz, String f2_bar ) {
this.f1_baz = f1_baz;
this.f2_bar = f2_bar;
}
}
jsonRequest will be { "f1_baz ":"baz", "f2_bar":"bar" }
Here's my solution for looping over json text files in a given directory and writing over the top of them with sorted versions:
private void standardizeFormat(File dir) throws IOException {
File[] directoryListing = dir.listFiles();
if (directoryListing != null) {
for (File child : directoryListing) {
String path = child.getPath();
JsonReader jsonReader = new JsonReader(new FileReader(path));
Gson gson = new GsonBuilder().setPrettyPrinting().registerTypeAdapter(LinkedTreeMap.class, new SortedJsonSerializer()).create();
Object data = gson.fromJson(jsonReader, Object.class);
JsonWriter jsonWriter = new JsonWriter(new FileWriter(path));
jsonWriter.setIndent(" ");
gson.toJson(data, Object.class, jsonWriter);
jsonWriter.close();
}
}
}
private class SortedJsonSerializer implements JsonSerializer<LinkedTreeMap> {
#Override
public JsonElement serialize(LinkedTreeMap foo, Type type, JsonSerializationContext context) {
JsonObject object = new JsonObject();
TreeSet sorted = Sets.newTreeSet(foo.keySet());
for (Object key : sorted) {
object.add((String) key, context.serialize(foo.get(key)));
}
return object;
}
}
It's pretty hacky because it depends on the fact that Gson uses LinkedTreeMap when the Type is simply Object. This is an implementation details that is probably not guaranteed. Anyway, it's good enough for my short-lived purposes...

Categories