I succeeded to parse my object to a JSON array string :
public class MyClass extends JsonElement {
private JsonArray array;
public MyClass(String name, String role, String title) {
this.array = new JsonArray();
this.array.add(Objects.requireNonNull(name));
this.array.add(Objects.requireNonNull(role));
this.array.add(Objects.requireNonNull(title));
}
#Override
public JsonElement deepCopy() {
return array.deepCopy();
}
#Override
public boolean isJsonArray() {
return true;
}
#Override
public JsonArray getAsJsonArray() {
return array;
}
}
Gson gson = new Gson();
MyClass obj = new MyClass("ab", "cd", "ef");
System.out.println(gson.toJson(obj)); // "["ab", "cd", "ef"]"
How can we do the opposite ?
MyClass obj2 = gson.fromJson("[\"ab\", \"cd\", \"ef\"]", MyClass.class);
I got :
Exception in thread "main" com.google.gson.JsonSyntaxException: Expected a MyClass but was com.google.gson.JsonArray
I tried :
public class MyAdapter extends TypeAdapter<MyClass> {
#Override
public void write(JsonWriter out, CommunityTeamMember value) throws IOException {
TypeAdapters.JSON_ELEMENT.write(out, value);
}
#Override
public MyClass read(JsonReader in) throws IOException {
JsonArray array = TypeAdapters.JSON_ELEMENT.read(in).getAsJsonArray();
return new MyClass(array.get(0).getAsString(),array.get(1).getAsString(),array.get(2).getAsString());
}
}
GsonBuilder builder = new GsonBuilder();
builder.registerTypeAdapter(MyClass.class, new MyAdapter());
Gson gson = builder.create();
You've done some trickery by making your object behave as a JsonArray to the serializer, so you get a JsonArray back, but the deserializer only knows to put it into a JsonArray. To get back an instance of the class you had, you can add a constructor that takes a JsonArray
class MyClass extends JsonElement {
private JsonArray array;
public MyClass(String name, String role, String title) {
this.array = new JsonArray();
this.array.add(Objects.requireNonNull(name));
this.array.add(Objects.requireNonNull(role));
this.array.add(Objects.requireNonNull(title));
}
public MyClass(JsonArray jsonArray) {
this.array = jsonArray;
}
#Override
public JsonElement deepCopy() {
return array.deepCopy();
}
#Override
public boolean isJsonArray() {
return true;
}
#Override
public JsonArray getAsJsonArray() {
return array;
}
}
public class Main {
public static void main(String[] args) {
Gson gson = new Gson();
MyClass obj = new MyClass("ab", "cd", "ef");
String serialized = gson.toJson(obj);
System.out.println(serialized); // "["ab", "cd", "ef"]"
MyClass obj2 = new MyClass(JsonParser.parseString(serialized).getAsJsonArray());
System.out.println(obj2.getAsJsonArray()); //["ab","cd","ef"]
}
}
Related
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;
}
}
}
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();
Given the following structure:
abstract class Message {
Message anotherMessage;
String attribute; //just random stuff
}
I would like to have the following json-string as output:
{type=Class.getSimpleName(), data=gson(Message)}
as Message is abstract and can have multiple implementations. The problem is, that "anotherMessage" wouldn't have the structure type,data.
My implementation of serialize:
public JsonElement serialize(final Message src, final Type typeOfSrc,
final JsonSerializationContext context) {
Gson gson = new Gson();
JsonObject elem = new JsonObject();
elem.addProperty("type", src != null ? src.getClass().getSimpleName() : null);
elem.addProperty("data", src != null ? gson.toJson(src) : null);
return elem;
}
How can I do this recursively? I cannot get a Gson-Object with already attached message adapter (stackoverflow-exception)
It's possible to use the JsonSerializationContext/JsonDeserializationContext during the serialization/deserialization to serialize/deserialize another object.
Message.java
abstract class Message {
Message anotherMessage;
String theMessage;
public Message getAnotherMessage() {
return anotherMessage;
}
public String getTheMessage() {
return theMessage;
}
}
Info.java
public class InfoMessage extends Message {
public InfoMessage(Message anotherMessage, String theMessage) {
this.anotherMessage = anotherMessage;
this.theMessage = theMessage;
}
}
Alert.java
public class AlertMessage extends Message {
public AlertMessage(Message anotherMessage, String theMessage) {
this.anotherMessage = anotherMessage;
this.theMessage = theMessage;
}
}
ErrorMessage.java
public class ErrorMessage extends Message {
public ErrorMessage(Message anotherMessage, String theMessage) {
this.anotherMessage = anotherMessage;
this.theMessage = theMessage;
}
}
MessageSerializer.java
public JsonElement serialize(Message src, Type typeOfSrc, JsonSerializationContext context) {
JsonObject elem = new JsonObject();
if (src == null) {
} else {
elem.addProperty("type", src.getClass().getSimpleName());
elem.addProperty("attribute", src.getTheMessage());
elem.add("data", src.anotherMessage != null ? context.serialize(src.anotherMessage, Message.class): null);
}
return elem;
}
Test.java
public static void main(String[] args) {
Gson gson = new GsonBuilder()
.registerTypeAdapter(Message.class, new MessageSerializer())
.setPrettyPrinting()
.create();
String json = gson.toJson(
new InfoMessage(
new AlertMessage(
new ErrorMessage(null, "the error message"),
"the alert message"),
"the info message"),
Message.class);
System.out.println(json);
}
I have following json:
{
"list": [
{
"BaseClass": {
"id": 0
}
},
{
"ExtendedClass1": {
"id": 1,
"sum": 100
}
},
{
"ExtendedClass1_1": {
"id": 2,
"sum": 200,
"expr": "text"
}
},
{
"ExtendedClass2": {
"id": 3,
"total": 300
}
}
]
}
Also I have following classes with declared inheritance:
MetaClass.java
import java.util.ArrayList;
import com.google.gson.Gson;
public class MetaClass{
public ArrayList<BaseClass> list = new ArrayList<BaseClass>();
public static void main(String[] args) {
String json = "{\"list\":[{\"BaseClass\":{\"id\":0}},{\"ExtendedClass1\":{\"id\":1,\"sum\":100}},{\"ExtendedClass1_1\":{\"id\":2,\"sum\":200,\"expr\":\"text\"}},{\"ExtendedClass2\":{\"id\":3,\"total\":300}}]}";
MetaClass gson = new Gson().fromJson(json, MetaClass.class);
}
}
BaseClass.java
public class BaseClass{
public int id;
public BaseClass() {
}
}
ExtendedClass1.java
public class ExtendedClass1 extends BaseClass{
public ExtendedClass1() {
}
public int sum;
}
ExtendedClass2.java
public class ExtendedClass2 extends BaseClass {
public ExtendedClass2() {
}
public int total;
}
ExtendedClass1_1.java
public class ExtendedClass1_1 extends ExtendedClass1 {
public ExtendedClass1_1() {
}
public String expr;
}
Also, there could be a lot of such classes with multilevel inheritance. I try to make this example simpler. How correctly parse mentioned json string? Assume please that I could not change input json, only I can change classes and write custom fromJson command somehow...
EDIT: Of course, I can add to BaseClass.java all attributes that could be met in json (see below), but it seems not elegant solution.
public ExtendedClass1 ExtendedClass1;
public ExtendedClass2 ExtendedClass2;
public ExtendedClass1_1 ExtendedClass1_1;
You can write a custom TypeAdapter and register it to gsonBuilder. In your custom type adapter's read method, you have to manage mappings to create correct instances of the classes that you defined. I used the tags of your list json array's items:
public class CustomTypeAdapter extends TypeAdapter<BaseClass> {
#Override
public BaseClass read(JsonReader in) throws IOException {
BaseClass item = null;
Gson gson = new Gson();
JsonParser jsonParser = new JsonParser();
JsonObject jo = (JsonObject)jsonParser.parse(in);
JsonElement je = null;
if ((je = jo.get("BaseClass")) != null) {
item = gson.fromJson(je, BaseClass.class);
} else if((je = jo.get("ExtendedClass1")) != null) {
item = gson.fromJson(je, ExtendedClass1.class);
} else if((je = jo.get("ExtendedClass1_1")) != null) {
item = gson.fromJson(je, ExtendedClass1_1.class);
} else if((je = jo.get("ExtendedClass2")) != null) {
item = gson.fromJson(je, ExtendedClass2.class);
}
return item;
}
#Override
public void write(JsonWriter out, BaseClass item) throws IOException {
throw new IllegalArgumentException("TypeAdapter.write method not implemented!");
}
}
Test:
String json = "{\"list\":[{\"BaseClass\":{\"id\":0}},{\"ExtendedClass1\":{\"id\":1,\"sum\":100}},{\"ExtendedClass1_1\":{\"id\":2,\"sum\":200,\"expr\":\"text\"}},{\"ExtendedClass2\":{\"id\":3,\"total\":300}}]}";
GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.registerTypeAdapter(BaseClass.class, new CustomTypeAdapter());
Gson gson = gsonBuilder.create();
java.lang.reflect.Type listType = new com.google.gson.reflect.TypeToken<List<BaseClass>>() {}.getType();
JsonArray jsonList = (JsonArray) (gson.fromJson(json, JsonObject.class).get("list"));
List<BaseClass> itemList = gson.fromJson(jsonList, listType);
To be clear, let introduse some model:
interface A {
boolean isSomeCase();
}
class AAdapter implements JsonSerializer<A> {
public JsonElement serialize(A src, Type typeOfSrc, JsonSerializationContext context) {
if (src.isSomeCase()) {
/* some logic */
return result;
} else {
JsonObject json = new JsonObject();
JsonElement valueJson = <???>; // TODO serialize src like POJO
json.add(src.getClass().getSimpleName(), valueJson);
return json;
}
}
}
Gson gson = new GsonBuilder()
.registerTypeHierarchyAdapter(A.class. new AAdapter())
.create();
How it is possible to serealize some instance of A, which isSomeCase() = false, like any other object, that is serialized by ReflectiveTypeAdapterFactory.Adapter.
You can write a custom TypeAdapterFactory and handle incoming object's isSomeCase() result in its TypeAdapter's write() method and apply your logic there:
public class ATypeAdapterFactory implements TypeAdapterFactory {
public TypeAdapter<A> create(Gson gson, TypeToken type) {
if (!A.class.isAssignableFrom(type.getRawType())) {
// Check if incoming raw type is an instance of A interface
return null;
}
final TypeAdapter<A> delegate = gson.getDelegateAdapter(this, type);
return new TypeAdapter<A>() {
#Override
public void write(JsonWriter out, A value) throws IOException {
if(value.isSomeCase()) {
// your custom logic here
out.beginObject();
out.name("x").value(0);
out.endObject();
} else {
// default serialization here
delegate.write(out, value);
}
}
#Override
public A read(JsonReader in) throws IOException {
return delegate.read(in);
}
};
}
}
Test:
final GsonBuilder gsonBuilder = new GsonBuilder();
// Register custom type adapter factory
gsonBuilder.registerTypeAdapterFactory(new ATypeAdapterFactory());
final Gson gson = gsonBuilder.create();
A aSomeCaseTrue = new AImpl(true);
System.out.print("aSomeCaseTrue:" + gson.toJson(aSomeCaseTrue));
// writes; aSomeCaseTrue:{"x":0}
A aSomeCaseFalse = new AImpl(false);
System.out.print("aSomeCaseFalse:" + gson.toJson(aSomeCaseFalse););
// writes; aSomeCaseFalse:{"someCase":false}
Extras:
1) Your interface:
interface A {
boolean isSomeCase();
}
2) A sample class which implements your sample interface:
class AImpl implements A {
boolean someCase;
public AImpl(boolean value) {
this.someCase = value;
}
#Override
public boolean isSomeCase() {
return someCase;
}
}