Can I deserialise a JSON string into an existing object? - java

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

Related

How to serialize/deserialize object to Map

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.

Gracefully ignore unknown class in Jackson JSON deserialization with type info?

Using the jackson JSON library, I'm able to generate JSON with typed field info and read it back. However, some clients of my JSON object may not have access to the particular class. In which I want them to just serialize the JSON attribute as a Map. That doesn't seem to work as demonstrated by the following codes. How to do this trick?
static class Foo {
#JsonProperty
private String x;
#JsonProperty
#JsonTypeInfo(use= JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY)
private Object o;
public String getX() {
return x;
}
public void setX(String x) {
this.x = x;
}
public Object getO() {
return o;
}
public void setO(Object o) {
this.o = o;
}
}
#Test
public void testJacksonTypedSerialization() throws Exception {
Foo foo = new Foo();
foo.setX("hello world!");
Foo foo2 = new Foo();
foo2.setX("inner");
foo.setO(foo2);
ObjectMapper mapper = new ObjectMapper();
String str = mapper.writeValueAsString(foo);
System.out.println("foo is written as " + str);
assertTrue(str.contains("Foo"));
assertTrue(str.contains("#class"));
Foo read = mapper.readValue(str, Foo.class);
assertEquals(read.getX(), foo.getX());
assertNotNull(read.getO());
assertTrue(read.getO() instanceof Foo);
String str2 = str.replace("Foo", "NoSuchType");
Foo read2 = mapper.readValue(str2, Foo.class);
assertEquals(read2.getX(), foo.getX());
assertNotNull(read2.getO());
// in this case, I want Jackson to read 'o' as Java Map/List
assertTrue(read.getO() instanceof Map);
// !!! encountered error
//com.fasterxml.jackson.databind.JsonMappingException: Invalid type id 'com.mycorp.NoSuchType' (for id type 'Id.class'): no such class found
}
Well, I think you can get progressively closer solutions as you put more effort in. In theory, you should be able to write a custom type resolver that returns unknown if the class does not exist (the default Id.CLASS resolver never does this). This quickly turned into something deeper than I had time to go into when I tried it, though.
A simpler solution is to configure the standard name resolver (which can return "type unknown") and specify a defaultImpl. Something like:
#JsonProperty
#JsonTypeInfo(defaultImpl=JsonNode.class, use= JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY)
#JsonSubTypes({ #JsonSubTypes.Type(name="foo-type", value=Foo.class) })
public Object o;
One thing I found is that it you specify defaultImpl as Map, then it doesn't serialize as a map, but tries to treat the Map class as a bean (and hence finds no properties). You could either keep hold of the JsonNode or use the object mapper to convert it on to a Map properly.
I'm not actually sure that kind of swapping is possible- once you're into deserializing in a type hierarchy, you might already be committed to deserializing as a bean? In which case you're heading into custom deserializer territory.
I tweaked the test a bit to get it passing with those annotations on 'o':
#Test
public void testJacksonTypedSerialization() throws Exception {
Foo foo = new Foo();
foo.setX("hello world!");
Foo foo2 = new Foo();
foo2.setX("inner");
foo.setO(foo2);
String str = mapper.writeValueAsString(foo);
System.out.println("foo is written as " + str);
assertThat(str, both(containsString("foo-type")).and(containsString("#type")));
Foo read = mapper.readValue(str, Foo.class);
assertThat(read.getX(), equalTo(foo.getX()));
assertThat(read.getO(), instanceOf(Foo.class));
String str2 = str.replace("foo-type", "NoSuchType");
Foo read2 = mapper.readValue(str2, Foo.class);
assertThat(read2.getX(), equalTo(foo.getX()));
assertThat(read2.getO(), instanceOf(ObjectNode.class));
}
For now, I will store the nested object as an explicit Map
// pseudo-codes
// writing out
Foo foo = new Foo();
Map objAsMap = mapper.read(mapper.writeAsString(inner), Map.class);
objAsMap.put("#advisoryJavaClass", inner.getClass().gerName());
foo.setObjMap(objAsMap);
String json = mapper.writeAsString(foo);
// reading back
Foo foo2 = mapper.read(json, Foo.class);
Object innerAttr = null;
if (foo2.getObjMap() != null) {
try {
Class cls = Class.forName(foo2.getObjectMap().get("#advistoryJavaClass");
innerAttr = mapper.read(mapper.writeAsString(foo2.getObjMap()), cls);
} catch(ex) { } // swallow all class look up error
}
Note: the advantage of storing the inner attribute object as Map instead of string is that the resulting JSON is much prettier. Long term solution is to wait for jackson to fix this.

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.

Trim all strings elements in a complex object

Can I write a generic method to trim all strings within an complex object (object containing other objects)? Should java reflection api be used to achieve this?Thanks.
I have provided a sample below. However in reality there could be multiple objects within objects. Each object might contain a collection of String or collection of other objects which may contain String. Is there a way to trim the Strings - ones directly with the objects and ones within collection.
public class School{
private List<Course> courses;
private List<Student> students;
// Getters and Setters
}
public class Course{
private String name;
private String xxx;
private String yyy;
private List<String> zzzList;
}
public class Student{
private Map<String,String> xxx;
private List<Course> courseList;
}
Yes, reflection is the way. Basically, you need to:
get the class of the top level object (with [object].getClass())
get all the fields of the object (with clazz.getFields() - beware, it works only with public fields)
check if the field is String (either get field.getType() and check it's a string, or do a field.get(the object) and a instanceof String)
if it's the case, replace the string in the object with the trimmed one, using field.set([your object],[trimmed string])
if the field is an object but not a string, call your method recursively
That will do the trick.
---- just seen your update
Trimming strings in collection will be more tricky, since the strings are not exposed as public fields of the collection (List for example).
You will need something more clever, that will check if an object is an instance of List, or Map, or etc... (or a derived class!).
Main problem is also that java generics are done with erasing type at compile type. So you cannot know that your field is List[String] or List[Integer] or whatever. Every List[?] becomes List.
Still you can try to do it like that:
if field type is List
iterate through the list values
if a value is instanceof String, you have to remove it from the list and insert in place the trimmed version
if a value is an object, there you go again recursively with your method.
Not very interesting in real life samples, but more on a library side maybe.
Long way to go though!
Yes, you can do that with reflection, quite easily. Just check if the field is instanceof String.
The exact way to do it depends on your object structure.
/*********************************************************************************************
* Trim first level children of string type in this object
* #param obj which all string properties to be trimmed
*********************************************************************************************/
public static void trimAll(final Object obj)
throws LocalException
{
if (obj==null) return;
final Class c = obj.getClass();
final Method[] methods = c.getMethods();
final Class[] SETTER_ARGS = new Class[]{String.class};
final Object[] SETTER_VAL = new Object[1];
final String SET = "set";
final String GET = "get";
final String SPACE = "\u0020";
final String TAB = "\t";
for (final Method m:methods)
{
try
{
final String name=m.getName();
if (
name.length()>GET.length()
&& name.indexOf(GET)==0
&& m.getReturnType().equals(String.class)
&& m.getParameterTypes().length==0)
{
final String v = (String)m.invoke(obj);
if (v!=null && (v.contains(SPACE) || v.contains(TAB)) )
{
final Method setter=c.getMethod(SET+name.substring(3),SETTER_ARGS);
if (setter!=null)
{
SETTER_VAL[0]=v.trim();
setter.invoke(obj,SETTER_VAL);
}
}
}
}
catch (final Throwable e)
{
throw new LocalException(LocalException.EC_GENERAL_EXCEPTION,e);
}
}
}
We can also use Jackson to serialize and then deserialize the object. While deserializing we can use custom deserializer to trim all the String values.
Create a deserializer like this:
public class TrimStringToNullDeserializer extends JsonDeserializer<String> {
#Override
public String deserialize(JsonParser jsonParser, DeserializationContext deserializationContext)
throws IOException {
String value = jsonParser.getValueAsString();
if (isNull(value)) {
return null;
}
value = value.trim();
if (value.length() == 0) {
value = null;
}
return value;
}
And then we can use Jackson to trim all values:
public class TrimStringToNullConfiguration {
private ObjectMapper objectMapper;
public Client trimToNull(Client inputClient) throws JsonProcessingException {
return getObjectMapper().readValue(getObjectMapper().writeValueAsString(inputClient), Client.class);
}
private ObjectMapper getObjectMapper() {
if (isNull(objectMapper)) {
objectMapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
module.addDeserializer(String.class, new TrimStringToNullDeserializer());
objectMapper.registerModule(module);
}
return objectMapper;
}
I have placed a working example over here.
private <T> T toTrim(T t) {
Field[] fields = t.getClass().getFields();
for (Field field : fields) {
try {
if (field.get(t) instanceof String) {
Object o = field.get(t);
String s = (String) o;
field.set(t, s.trim().toUpperCase());
}
} catch (IllegalAccessException e) {
log.info("Error converting field "+ field.getName() );
}
}
return t;
}
if (yourObject instanceof String){
yourObject = yourObject.trim();
}
Hope it helps :)

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