Jackson Databind ObjectMapper convertValue with custom mapping implementaion - java

Foo1
public class Foo1{
private Long id;
private String code;
private String name;
private Boolean rState;
private String comments; // Contain json data
private String attachments; // Contain json data
}
Foo2
public class Foo2{
private Long id;
private String code;
private String name;
private Boolean rState;
private List comments;
private List attachments;
}
convert value
new ObjectMapper().convertValue(foo1, Foo2.class);
is it possible, when I call convert value, json String automatically convert to list ?

I would recommend going with a Deserializer as Foo1 might actually be serialized elsewhere and we might only have it's serialized data to convert. And that is what is our goal here.
Create a SimpleModule that deserializes List type
public class ListDeserializer extends StdDeserializer<List> {
public ListDeserializer() {
super(List.class);
}
#Override
public List deserialize(JsonParser p, DeserializationContext ctxt)
throws IOException {
if (p.getCurrentName().equals("result") || p.getCurrentName().equals("attachments")) {
JsonNode node = p.getCodec().readTree(p);
return new ObjectMapper().readValue(node.asText(), List.class);
}
return null;
}
}
The above deserializer only recognizes result and attachments and ignores the rest. So this is a very specific deserializer for List in Foo2 class.
My test Foo1 and Foo2 are as follows
public class Foo1 {
String result = "[\"abcd\", \"xyz\"]";
public String getResult() {
return result;
}
public void setResult(String result) {
this.result = result;
}
}
public class Foo2 {
List result;
public List getResult() {
return result;
}
public void setResult(List result) {
this.result = result;
}
}
I tested the above code as follows
// Module to help deserialize List objects
SimpleModule listModule = new SimpleModule();
listModule.addDeserializer(List.class, new ListDeserializer());
ObjectMapper objectMapper = new ObjectMapper().registerModules(listModule);
Foo2 foo2 = objectMapper.convertValue(new Foo1(), Foo2.class);
System.out.println(foo2.getResult());
And the output is
[abcd, xyz]

Option 1 : Just annotate the getter method of the Foo1.class
#JsonProperty("comments")
public List getComments() {
List list = new ArrayList();
list.add(comments);
return list;
}
#JsonProperty("attachments")
public List getAttachments() {
List list = new ArrayList();
list.add(attachments);
return list;
}
Foo1 foo1 = new Foo1(Long.valueOf(1),"a","aaa",true,"abc","def");
System.out.println(new ObjectMapper().writeValueAsString(foo1));
Foo2 foo2 = new ObjectMapper().convertValue(foo1, Foo2.class);
System.out.println(new ObjectMapper().writeValueAsString(foo2));
Option 2 : use jackson-custom-serialization
ObjectMapper mapper = new ObjectMapper();
SimpleModule simpleModule = new SimpleModule();
simpleModule.addSerializer(Foo1.class,new ListSerializer());
mapper.registerModule(simpleModule);
public class ListSerializer extends StdSerializer<Foo1> {
public ListSerializer() {
this(null);
}
protected ListSerializer(Class<Blah.Foo1> t) {
super(t);
}
public void serialize(Blah.Foo1 foo1, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
jsonGenerator.writeStartObject();
List list = new ArrayList();
list.add(foo1.getComments());
List list1 = new ArrayList();
list1.add(foo1.getAttachments());
jsonGenerator.writeObjectField("id",foo1.getId());
jsonGenerator.writeObjectField("code",foo1.getCode());
jsonGenerator.writeObjectField("name",foo1.getName());
jsonGenerator.writeObjectField("rState",foo1.getrState());
jsonGenerator.writeObjectField("comments",list);
jsonGenerator.writeObjectField("attachments",list1);
jsonGenerator.writeEndObject();
}
}
Foo1 foo1 = new Foo1(Long.valueOf(1),"a","aaa",true,"abc","def");
System.out.println(mapper.writeValueAsString(foo1));
Foo2 foo2 = mapper.convertValue(foo1, Foo2.class);
System.out.println(new ObjectMapper().writeValueAsString(foo2));

Related

Jackson deserialize objects as the same one

I was working with jackson's JsonIdentityInfo annatation, the serialization looks good. But the deserialization didnt work as I expected, the objects sharing the same id, are not deserialized into the same objects. Below are the class definitions.
class ParameterResolver implements ObjectIdResolver {
private final Map<ObjectIdGenerator.IdKey,Object> items = new HashMap<>();
#Override
public void bindItem(ObjectIdGenerator.IdKey id, Object pojo) {
if (items.containsKey(id)) {
throw new IllegalStateException("Already had POJO for id (" + id.key.getClass().getName() + ") [" + id
+ "]");
}
items.put(id, pojo);
}
#Override
public Object resolveId(ObjectIdGenerator.IdKey id) {
Object object = items.get(id);
return object == null ? getById(id) : object;
}
protected Object getById(ObjectIdGenerator.IdKey id){
Object object;
try {
object = id.scope.getConstructor().newInstance();
id.scope.getMethod("setId", Integer.class).invoke(object, (Integer) id.key);
} catch (Exception e) {
throw new IllegalStateException(e);
}
items.put(id, object);
return object;
}
#Override
public ObjectIdResolver newForDeserialization(Object context) {
return new ParameterResolver();
}
#Override
public boolean canUseFor(ObjectIdResolver resolverType) {
return resolverType.getClass() == getClass();
}
}
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, resolver = ParameterResolver.class, property = "id", scope = Parameter.class)
class Parameter {
private Integer id;
private String data;
public Parameter() {}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getData() {
return data;
}
public void setData(String data) {
this.data = data;
}
}
class Container {
public Parameter p;
public Container() {}
public Container(Parameter p) {
this.p = p;
}
}
and this is the unit test
#Test
public void test() throws JsonProcessingException {
Parameter p1 = new Parameter(), p2 = new Parameter();
p1.setId(1);
p1.setData("1");
List<Container> list = new ArrayList<>();
list.add(new Container(p1));
list.add(new Container(p1));
ObjectMapper mapper = new ObjectMapper();
String content = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(list);
List<Container> listD = mapper.readValue(content, new TypeReference<List<Container>>() {});
assertSame(listD.get(0).p, list.get(1).p); // didnt pass this assertion
}
Deserialization creates new objects from JSON. Objects sharing the same id are deserialized into the same objects. However, these are different objects than those that were previously serialized.
In your assertion you are comparing a deserialized parameter instance with an instance that was used for serialization:
assertSame(listD.get(0).p, list.get(1).p); // didnt pass this assertion
You should compare the two deserialized instances instead:
assertSame(listD.get(0).p, listD.get(1).p);
Please note the 'D' in the second parameter listD.get(1).p.

Deserializing interface data using gson not getting the value back?

I'm running a simple experiment test below.
public class MyTest {
#Test
public void testing() {
GsonBuilder builder = new GsonBuilder();
builder.registerTypeAdapter(SubData.class, new SubDataImplInstanceCreator());
Gson gson = builder.create();
Dataclass data = new Dataclass();
data.key1 = "abc";
SubDataImpl subData = new SubDataImpl();
subData.hello = "ttt";
data.sub = subData;
String jsonValue = gson.toJson(data);
System.out.println(jsonValue);
Dataclass data2 = gson.fromJson(jsonValue, Dataclass.class);
System.out.println(gson.toJson(data2));
}
class Dataclass implements Serializable {
String key1;
SubData sub;
}
interface SubData {
String getHello();
}
class SubDataImpl implements SubData, Serializable {
String hello;
#Override
public String getHello() {
return hello;
}
}
public class SubDataImplInstanceCreator implements InstanceCreator<SubDataImpl> {
#Override
public SubDataImpl createInstance(Type type) {
return new SubDataImpl();
}
}
}
I'm expecting it to return
{"key1":"abc","sub":{"hello":"ttt"}}
{"key1":"abc","sub":{"hello":"ttt"}}
As they are essentially the same data that get serialized and deserialized.
However, when I run it, I got
{"key1":"abc","sub":{"hello":"ttt"}}
{"key1":"abc","sub":{}}
Why did I loose away my SubData value, after deserializing the Json String? Did I miss anything in my code?
It seems you have hit this bug , the suggested solution is to use a TypeAdapter for the interface.
Quick and dirty implementation (use it in place ofSubDataImplInstanceTypeAdapter)
public class SubDataImplInstanceTypeAdapter implements JsonDeserializer<SubDataImpl>, JsonSerializer<SubDataImpl> {
#Override
public SubDataImpl deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
SubDataImpl impl = new SubDataImpl();
JsonObject object = json.getAsJsonObject();
impl.setHello(object.get("hello").getAsString());
return impl;
}
#Override
public JsonElement serialize(SubDataImpl src, Type typeOfSrc, JsonSerializationContext context) {
return context.serialize(src);
}
}
I'm using the below
public class MyTest {
#Test
public void testing() {
GsonBuilder builder = new GsonBuilder();
builder.registerTypeAdapter(SubData.class, new SubDataTypeAdapter());
Gson gson = builder.create();
Dataclass data = new Dataclass();
data.key1 = "abc";
SubDataImpl subData = new SubDataImpl();
subData.hello = "ttt";
data.sub = subData;
String jsonValue = gson.toJson(data);
System.out.println(jsonValue);
Dataclass data2 = gson.fromJson(jsonValue, Dataclass.class);
System.out.println(gson.toJson(data2));
}
class SubDataTypeAdapter extends TypeAdapter<SubDataImpl> {
#Override
public void write(JsonWriter out, final SubDataImpl subData) throws IOException {
out.beginObject();
out.name("hello").value(subData.getHello());
out.endObject();
}
#Override
public SubDataImpl read(JsonReader in) throws IOException {
final SubDataImpl subData = new SubDataImpl();
in.beginObject();
while (in.hasNext()) {
switch (in.nextName()) {
case "hello":
subData.hello = in.nextString();
break;
}
}
in.endObject();
return subData;
}
}
class Dataclass implements Serializable {
String key1;
SubData sub;
}
abstract class SubData {
abstract String getHello();
}
class SubDataImpl extends SubData implements Serializable {
String hello;
#Override
public String getHello() {
return hello;
}
}
}

Java GSON - serialize int as strings to json file

I have this Java class:
class Car {
int mileage;
int id;
}
When I tell gson to serialize it, it of course serializes it to:
{
"mileage": 123,
"id": 12345678
}
But what if I want to serialize it to:
{
"mileage": "123",
"id": "12345678"
}
Assuming changing my members from int to String, is not an option, is there a way to tell gson to serialize those int members as strings to the json file?
There are likely many ways to achieve what you desire.
I will share two ways.
FIRST - Using Custom Serialization
SECOND - Using JsonAdapter Annotation - More Simple
Using a custom serialization
public static class CarSerializer implements JsonSerializer<Car> {
public JsonElement serialize(final Car car, final Type type, final JsonSerializationContext context) {
JsonObject result = new JsonObject();
result.add("mileage", new JsonPrimitive(Integer.toString(car.getMileage())));
result.add("id", new JsonPrimitive(Integer.toString(car.getId())));
return result;
}
}
To call this, simply adapt your code or use the following code with a constructor
Car c = new Car(123, 123456789);
com.google.gson.Gson gson = new
GsonBuilder().registerTypeAdapter(Car.class, new CarSerializer())
.create();
System.out.println(gson.toJson(c));
The output should be
{"mileage":"123","id":"12345678"}
Full Code for Example 1:
public class SerializationTest {
public static class Car {
public int mileage;
public int id;
public Car(final int mileage, final int id) {
this.mileage = mileage;
this.id = id;
}
public int getId() {
return id;
}
public void setId(final int id) {
this.id = id;
}
public String getMileage() {
return mileage;
}
public void setMileage(final String mileage) {
this.mileage = mileage;
}
}
public static class CarSerializer implements JsonSerializer<Car> {
public JsonElement serialize(final Car car, final Type type, final JsonSerializationContext context) {
JsonObject result = new JsonObject();
result.add("mileage", new JsonPrimitive(Integer.toString(car.getMileage())));
result.add("id", new JsonPrimitive(Integer.toString(car.getId())));
return result;
}
}
public static void main(final String[] args) {
Car c = new Car(123, 123456789);
com.google.gson.Gson gson = new
GsonBuilder().registerTypeAdapter(Car.class, new CarSerializer())
.create();
System.out.println(gson.toJson(c));
}
}
Using a #JsonAdapter annotation
Use the JsonAdapter Annotation on the Car class
#JsonAdapter(CarAdapter.class)
public class Car {
public int mileage;
public int id;
}
Create the Custom Adapter
public class CarAdapter extends TypeAdapter<Car> {
#Override
public void write(JsonWriter writer, Car car) throws IOException {
writer.beginObject();
writer.name("mileage").value(car.getMileage());
writer.name("id").value(car.getId());
writer.endObject();
}
#Override
public Car read(JsonReader in) throws IOException {
// do something you need
return null;
}
}
To serialize, using this method, use something like this
Car c = new Car(123, 123456789);
Gson gson = new Gson();
String result = gson.toJson(c);
Printing result in this case should output
{"mileage":"123","id":"12345678"}
You may try it this way:
new GsonBuilder()
.setFieldNamingPolicy(FieldNamingPolicy.UPPER_CAMEL_CASE)
.registerTypeAdapter(Integer.class, (JsonSerializer<Integer>)
(integer, type, jsonSerializationContext) -> new
JsonPrimitive(String.valueOf(integer)))
.excludeFieldsWithoutExposeAnnotation().create();

Jackson : map nested object

Using jackson, i wonder if it's possible du map json to Java with nested Object that are not like the json structure.
Here an exemple of what i want to do.
Json :
{
a = "someValue",
b = "someValue",
c = "someValue"
}
Java :
public class AnObject {
#JsonProperty("a")
private String value;
//Nested object
private SomeObject;
}
public class SomeObject {
#JsonProperty("b")
private String value1;
#JsonProperry("c")
private String value2;
}
Is it possible ?
Use the JsonUnwrapped annotation:
#JsonUnwrapped
private final SomeObject someObject;
which unwrappes all of SomeObject's fields into the parent, resulting in the following when serializing:
{"a":"foo","b":"bar","c":"baz"}
Using ObjectMapper you can convert JSON string to Object.
Use JsonUnwrapped in your AnObject class over someObject field.
#JsonUnwrapped
private SomeObject someObject;
then read JSON string and convert it to AnObject.
ObjectMapper mapper = new ObjectMapper();
try {
AnObject anObject1 = mapper.readValue(jsonString, AnObject.class);
} catch (IOException e) {
e.printStackTrace();
}
First of all, this is a JSON object.
It's an object literal.
Second of all, that is not a valid formatted object literal.
The correct one is this:
{ "a" : "someValue", "b": "someValue", "c": "someValue"}
Next, as sayd in comments, you have to define your own deserializer.
Main:
public static void main(String[] args) throws JsonParseException, JsonMappingException, IOException {
String json = "{\"a\" : \"someValue\",\"b\" : \"someValue\",\"c\" : \"someValue\"}";
final ObjectMapper om =
new ObjectMapper();//
om.registerSubtypes(AnObject.class);
SimpleModule module = new SimpleModule();
module.addDeserializer(AnObject.class, new CustomDeserializer2());
om.registerModule(module);
AnObject ob = om.readValue(json, AnObject.class);
System.out.println(ob.getValue());
System.out.println(ob.getObject().getValue1());
System.out.println(ob.getObject().getValue2());
}
Deserializer:
public class CustomDeserializer2 extends StdDeserializer<AnObject> {
private static final long serialVersionUID = -3483096770025118080L;
public CustomDeserializer2() {
this(null);
}
public CustomDeserializer2(Class<?> vc) {
super(vc);
}
#Override
public AnObject deserialize(JsonParser jp, DeserializationContext ctxt)
throws IOException, JsonProcessingException {
JsonNode interNode = jp.getCodec().readTree(jp);
AnObject ob = new AnObject();
if (interNode.get("a") != null) {
ob.setValue(interNode.get("a").toString());
}
SomeObject obj = new SomeObject();
if (interNode.get("b") != null) {
obj.setValue1(interNode.get("b").toString());
}
if (interNode.get("c") != null) {
obj.setValue2(interNode.get("c").toString());
}
ob.setObject(obj);
return ob;
}
Model: Pay attention to #JsonProperty on A field
public class AnObject {
#JsonProperty("a")
private String value;
private SomeObject object;
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
public SomeObject getObject() {
return object;
}
public void setObject(SomeObject arg) {
object = arg;
}
public class SomeObject {
private String value1;
private String value2;
public String getValue1() {
return value1;
}
public void setValue1(String value1) {
this.value1 = value1;
}
public String getValue2() {
return value2;
}
public void setValue2(String value2) {
this.value2 = value2;
}
Bye

GSON GraphAdapterBuilder fails with interfaces

I am trying to use GraphAdapterBuilder which is an extra to the GSON library to serialize an object with cyclic references. It works great for class but fails when trying to deserialize an interface.
To deserialize interface( which GSON doesn't do by default ) I am using PropertyBasedInterfaceMarshal or InterfaceAdapter. These are registered as custom type adapters for the interfaces.
When using ether above both fail to deserialize the interface as they are only passed the graph id like "0x4" as generated by GraphAdapterBuilder. This is passed as the JsonElement in the deserializer. Obviously there is nothing that can be done with this id from within the deserializer.
Shouldn't these be caught by the GraphAdapterBuilder instead of trying to be deserialized? I have not been able to get around this, is this a bug with GraphAdapterBuilder or is there a way to get around this?
Ok, this is a (working) stub for a solution. It's too late in Italy, to make it nicer.
You need a delegate function like this
package com.google.gson.graph;
/**
* #author Giacomo Tesio
*/
public interface GenericFunction<Domain, Codomain> {
Codomain map(Domain domain);
}
a TypeAdapterFactory like this:
package com.google.gson.graph;
import com.google.gson.Gson;
import com.google.gson.TypeAdapter;
import com.google.gson.TypeAdapterFactory;
import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonToken;
import com.google.gson.stream.JsonWriter;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
/**
* #author Giacomo Tesio
*/
public class InterfaceAdapterFactory implements TypeAdapterFactory {
final Map<String, GenericFunction<Gson, TypeAdapter<?>>> adapters;
private final Class<?> commonInterface;
public InterfaceAdapterFactory(Class<?> commonInterface, Class<?>[] concreteClasses)
{
this.commonInterface = commonInterface;
this.adapters = new HashMap<String, GenericFunction<Gson, TypeAdapter<?>>>();
final TypeAdapterFactory me = this;
for(int i = 0; i < concreteClasses.length; ++i)
{
final Class<?> clazz = concreteClasses[i];
this.adapters.put(clazz.getName(), new GenericFunction<Gson, TypeAdapter<?>>(){
public TypeAdapter<?> map(Gson gson) {
TypeToken<?> type = TypeToken.get(clazz);
return gson.getDelegateAdapter(me, type);
}
});
}
}
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
final TypeAdapter<T> delegate = gson.getDelegateAdapter(this, type);
if(!this.commonInterface.isAssignableFrom(type.getRawType())
&& !this.commonInterface.equals(type.getRawType()))
{
return delegate;
}
final TypeToken<T> typeToken = type;
final Gson globalGson = gson;
return new TypeAdapter<T>() {
public void write(JsonWriter out, T value) throws IOException {
out.beginObject();
out.name("#t");
out.value(value.getClass().getName());
out.name("#v");
delegate.write(out, value);
out.endObject();
}
#SuppressWarnings({"unchecked"})
public T read(JsonReader in) throws IOException {
JsonToken peekToken = in.peek();
if(peekToken == JsonToken.NULL) {
in.nextNull();
return null;
}
in.beginObject();
String dummy = in.nextName();
String typeName = in.nextString();
dummy = in.nextName();
TypeAdapter<?> specificDelegate = adapters.get(typeName).map(globalGson);
T result = (T)specificDelegate.read(in);
in.endObject();
return result;
}
};
}
}
a pair of tests like these
public final class InterfaceAdapterFactoryTest extends TestCase {
public void testInterfaceSerialization1(){
SampleInterface first = new SampleImplementation1(10);
SampleInterfaceContainer toSerialize = new SampleInterfaceContainer("container", first);
GsonBuilder gsonBuilder = new GsonBuilder();
new GraphAdapterBuilder()
.addType(SampleInterfaceContainer.class)
.addType(SampleImplementation1.class)
.addType(SampleImplementation2.class)
.registerOn(gsonBuilder);
gsonBuilder.registerTypeAdapterFactory(new InterfaceAdapterFactory(
SampleInterface.class, new Class<?>[] { SampleImplementation1.class, SampleImplementation2.class }
));
Gson gson = gsonBuilder.create();
String json = gson.toJson(toSerialize);
System.out.println(json);
SampleInterfaceContainer deserialized = gson.fromJson(json, SampleInterfaceContainer.class);
assertNotNull(deserialized);
assertEquals(toSerialize.getName(), deserialized.getName());
assertEquals(toSerialize.getContent().getNumber(), deserialized.getContent().getNumber());
}
public void testInterfaceSerialization2(){
SampleImplementation2 first = new SampleImplementation2(5, "test");
SampleInterfaceContainer toSerialize = new SampleInterfaceContainer("container", first);
first.Container = toSerialize;
GsonBuilder gsonBuilder = new GsonBuilder();
new GraphAdapterBuilder()
.addType(SampleInterfaceContainer.class)
.addType(SampleImplementation1.class)
.addType(SampleImplementation2.class)
.registerOn(gsonBuilder);
gsonBuilder.registerTypeAdapterFactory(new InterfaceAdapterFactory(
SampleInterface.class, new Class<?>[] { SampleImplementation1.class, SampleImplementation2.class }
));
Gson gson = gsonBuilder.create();
String json = gson.toJson(toSerialize);
System.out.println(json);
SampleInterfaceContainer deserialized = gson.fromJson(json, SampleInterfaceContainer.class);
assertNotNull(deserialized);
assertEquals(toSerialize.getName(), deserialized.getName());
assertEquals(5, deserialized.getContent().getNumber());
assertEquals("test", ((SampleImplementation2)deserialized.getContent()).getName());
assertSame(deserialized, ((SampleImplementation2)deserialized.getContent()).Container);
}
}
and some sample classes (to verify that the tests pass)
public class SampleInterfaceContainer {
private SampleInterface content;
private String name;
public SampleInterfaceContainer(String name, SampleInterface content)
{
this.name = name;
this.content = content;
}
public String getName()
{
return this.name;
}
public SampleInterface getContent()
{
return this.content;
}
}
public interface SampleInterface {
int getNumber();
}
public class SampleImplementation1 implements SampleInterface{
private int number;
public SampleImplementation1()
{
this.number = 0;
}
public SampleImplementation1(int number)
{
this.number = number;
}
public int getNumber()
{
return this.number;
}
}
public class SampleImplementation2 implements SampleInterface{
private int number;
private String name;
public SampleInterfaceContainer Container;
public SampleImplementation2()
{
this.number = 0;
this.name = "";
}
public SampleImplementation2(int number, String name)
{
this.number = number;
this.name = name;
}
public int getNumber()
{
return this.number;
}
public String getName()
{
return this.name;
}
}
While this has been a quick&dirty hack, it works like a charme.
You just have to pay attention at the order of the operations during the initialization of GsonBuilder. You have to initialize and register the GraphAdapterBuilder first and only after register this factory.
It has been funny (if a bit tricky since I'm not a Java expert).

Categories

Resources