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();
Related
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));
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;
}
}
}
So I am parsing a VERY large JSON file with the use of GSON.
The class I'm parsing it into is structure like this:
What I'm trying to do is round the doubles (in the HashSet, in the Geometry class) up to to 4 decimal points. So as doubles are being added to the HashSet, I want to round them up.
public class Contours {
public String name = null;
public String type = null;
ArrayList<Features> features = null;
class Features {
public String type = null;
public Geometry geometry = null;
public Properties properties = null;
}
class Geometry {
public String type = null;
HashSet<double[]> coordinates = null;
}
class Properties {
public String CONTOUR = null;
public int OBJECTID;
public String LAYER = null;
public double ELEVATION;
}
}
Why I can't do this iteratively after GSON has parsed the file?
The file is VERY large, and has 412,064 lines and is 27.5mb large. So doing that will take very long time.
NOTE: this parsing happens every time this app is run, so speed is necessary.
Thanks
You can register a TypeAdapter to modify values as they're read:
public class GsonDoubleAdapterTest {
public static void main(String[] args) {
GsonBuilder builder = new GsonBuilder();
builder.registerTypeAdapter(Double.class, new DoubleAdapter());
Gson gson = builder.create();
Foo foo = gson.fromJson("{\"baz\": 0.123456}", Foo.class);
System.out.println(foo);
}
}
/**
* A type adapter that rounds doubles during read.
*/
class DoubleAdapter extends TypeAdapter<Double> {
#Override
public void write(JsonWriter out, Double value) throws IOException {
out.value(value);
}
#Override
public Double read(JsonReader in) throws IOException {
return new BigDecimal(in.nextDouble()).setScale(4, RoundingMode.HALF_UP).doubleValue();
}
}
class Foo {
private Double baz;
public Double getBaz() {
return baz;
}
public void setBaz(Double baz) {
this.baz = baz;
}
#Override
public String toString() {
return "Foo[baz=" + baz + ']';
}
}
I have a class with some attributes and in some cases I need to serialize only some of its fields and in other cases other fields. Let's say:
public class MyClass {
private String a;
private String b;
private String c;
private String d;
//constructor
public static MyClass fromString(final String string) {
Gson gson = new Gson();
MyClass obj = gson.fromJson(string, MyClass.class);
return obj;
}
public String toStringVersion1() {
final Gson gson = new GsonBuilder().create();
//here I want to serialize only a and b fields
}
public String toStringVersion2() {
final Gson gson = new GsonBuilder().create();
//here I want to serialize only c and d fields
}
}
I tried to use ExclusionStrategy from GSON, I defined a strategy for each version but I have to provide field names as parameter to strategy's constructor:
public class TestExclStrat implements ExclusionStrategy {
private Set<String> toBeSerialized;
public TestExclStrat(Set<String> fields) {
toBeSerialized = fields;
}
#Override
public boolean shouldSkipClass(Class<?> arg0) {
return false;
}
#Override
public boolean shouldSkipField(FieldAttributes f) {
return !toBeSerialized.contains(f.getName());
}
}
As far as I know, there is no way to get fieldNames at runtime in Java and I can't afford hardcoding name of the fields ("a", "b" for the first strategy and "c" and "d" for the second one). Any ideas?
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).