GSON serialization fails with custom Objects - java

I have the following class
public class Strassennetz {
private ObservableMap<Position, Strassenabschnitt> abschnitte;
private Map<Position, List<Auto>> autos;
private SimpleListProperty<Auto> autoList;
private BooleanProperty simuliert;
private String name;
public static Strassennetz instance;
...
}
which I want to serialize and deserialize with GSON/FxGson:
Gson gsonBuilder = FxGson.coreBuilder()
.registerTypeAdapter(Strassenabschnitt.class, StrassenAdapter.getInstance())
.enableComplexMapKeySerialization()
.setPrettyPrinting()
.create();
String jsonResult = gsonBuilder.toJson(instance);
The StrassenAdapter was necessary to (de-)serialize the abstract class Strassenabschnitt correctly.
That serialization works as expected when I set the fields "autos" and "autoList" transient.
As soon as I want to include those fields in my serialization (which is very important), I get the following exception:
Exception in thread "JavaFX Application Thread"
java.lang.IllegalArgumentException: class
com.sun.javafx.util.WeakReferenceQueue$ListEntry declares multiple
JSON fields named next
The class Auto looks like that:
public class Auto {
public enum AutoModell {ROT, POLIZEI, BLAU}
private int geschwindigkeit;
private static final int MAXGESCHWINDIGKEIT = 8;
private SimpleObjectProperty<Himmelsrichtung> richtung = new SimpleObjectProperty<>();
private Queue<Wendepunkt> wendepunkte;
private SimpleIntegerProperty positionX;
private SimpleIntegerProperty positionY;
private int breite;
private int laenge;
private AutoModell autoModell;
private final transient Strassennetz strassennetz;
private Rectangle rectangle;
...
}
I went through three google search result pages looking for an answer, but I do not get it to work.

GSON really does not play nicely with JavaFX properties, because it fails to properly respect encapsulation. The default way GSON serializes and object is to use reflection to recursively get the values of fields, rather than getting values of properties (as defined by get/set methods).
In a JavaFX application, JavaFX properties are typically used in the data model to implement "enhanced java beans" (where the enhancement is the ability to register listeners with the properties, etc.)
Consider a typical JavaFX bean-type class:
public class Item {
private final StringProperty name = new SimpleStringProperty();
private final IntegerProperty value = new SimpleIntegerProperty();
public StringProperty nameProperty() {
return name ;
}
public final String getName() {
return nameProperty().get();
}
public final void setName(String name) {
nameProperty().set(name);
}
public IntegerProperty valueProperty() {
return value ;
}
public final int getValue() {
return valueProperty().get() ;
}
public final void setValue(int value) {
valueProperty().set(value);
}
}
If you imagine serializing instances of this class "by hand", you would not be interested in the internal implementation of the name and value properties, or any listeners registered on those properties; you would be only interested in serializing the values represented by the properties (i.e. the values returned by getName() and getValue()). To deserialize an Item instance, you would simply instantiate an Item, and call setName() and setValue() with the serialized values.
If you try using GSON "as-is" to serialize, say, a list of such Item instances:
public class App {
public static void main(String[] args) throws Exception {
Random rng = new Random();
rng.setSeed(42);
List<Item> items = new ArrayList<>();
for (int i = 1 ; i <= 5 ; i++) {
Item item = new Item();
item.setName("Item "+i);
item.setValue(rng.nextInt(100));
item.valueProperty().addListener((obs, oldValue, newValue) -> System.out.println(newValue));
items.add(item);
}
Gson gson = new GsonBuilder().setPrettyPrinting().create();
String gsonJson = gson.toJson(items);
System.out.println(gsonJson);
}
}
You get the following:
[
{
"name": {
"name": "",
"value": "Item 1",
"valid": false
},
"value": {
"name": "",
"value": 30,
"valid": true,
"helper": {
"observable": {}
}
}
},
{
"name": {
"name": "",
"value": "Item 2",
"valid": false
},
"value": {
"name": "",
"value": 63,
"valid": true,
"helper": {
"observable": {}
}
}
},
{
"name": {
"name": "",
"value": "Item 3",
"valid": false
},
"value": {
"name": "",
"value": 48,
"valid": true,
"helper": {
"observable": {}
}
}
},
{
"name": {
"name": "",
"value": "Item 4",
"valid": false
},
"value": {
"name": "",
"value": 84,
"valid": true,
"helper": {
"observable": {}
}
}
},
{
"name": {
"name": "",
"value": "Item 5",
"valid": false
},
"value": {
"name": "",
"value": 70,
"valid": true,
"helper": {
"observable": {}
}
}
}
]
Notice how the internal elements of the StringProperty and IntegerProperty are serialized, including the listeners, which are almost certainly irrelevant to the data that you want to persist or transmit.
In your exception, you see the serialization of the listeners causing an exception (somewhere it appears you have a binding or an explicit weak listener registered on one or more of the properties: the weak listener cannot be serialized).
Worse, this cannot be deserialized:
List<Item> itemsFromGson = gson.fromJson(gsonJson, new TypeToken<List<Item>>() {}.getType());
generates an exception, because StringProperty and IntegerProperty cannot be constructed.
One solution here is to define custom serializers and deserializers for the StringProperty and IntegerProperty (and other Property) classes, which simply serialize and deserialize the contained value:
public class App {
public static void main(String[] args) throws Exception {
Random rng = new Random();
rng.setSeed(42);
List<Item> items = new ArrayList<>();
for (int i = 1 ; i <= 5 ; i++) {
Item item = new Item();
item.setName("Item "+i);
item.valueProperty().set(rng.nextInt(100));
item.valueProperty().addListener((obs, oldValue, newValue) -> System.out.println(newValue));
items.add(item);
}
GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.registerTypeAdapter(StringProperty.class, new JsonSerializer<StringProperty>() {
#Override
public JsonElement serialize(StringProperty src, Type typeOfSrc, JsonSerializationContext context) {
return new JsonPrimitive(src.get());
}
});
gsonBuilder.registerTypeAdapter(StringProperty.class, new JsonDeserializer<StringProperty>() {
#Override
public StringProperty deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) {
return new SimpleStringProperty(json.getAsJsonPrimitive().getAsString());
}
});
gsonBuilder.registerTypeAdapter(IntegerProperty.class, new JsonSerializer<IntegerProperty>() {
#Override
public JsonElement serialize(IntegerProperty src, Type typeOfSrc, JsonSerializationContext context) {
return new JsonPrimitive(src.get());
}
});
gsonBuilder.registerTypeAdapter(IntegerProperty.class, new JsonDeserializer<IntegerProperty>() {
#Override
public IntegerProperty deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) {
return new SimpleIntegerProperty(json.getAsJsonPrimitive().getAsInt());
}
});
Gson gson = gsonBuilder.setPrettyPrinting().create();
String gsonJson = gson.toJson(items);
System.out.println(gsonJson);
System.out.println("\n================\n");
List<Item> itemsFromGson = gson.fromJson(gsonJson, new TypeToken<List<Item>>() {}.getType());
System.out.println(itemsFromGson);
}
}
This version generates the expected
[
{
"name": "Item 1",
"value": 30
},
{
"name": "Item 2",
"value": 63
},
{
"name": "Item 3",
"value": 48
},
{
"name": "Item 4",
"value": 84
},
{
"name": "Item 5",
"value": 70
}
]
It's perhaps worth noting that the Jackson serialization libraries, by default, use "property access", i.e. they use get and set methods to serialize and deserialize the fields. Consequently, Jackson works very nicely with bean classes that follow the standard JavaFX Property pattern (like the Item class above) as long as the properties are all read/write (i.e. they have corresponding get and set methods); extra work is needed for read-only properties.

I just had to put Rectangle (in my Auto-class) as a transient variable. FxGson can handle JavaFX-Properties, but not Shape instances. So I ignored that field on serialization and made sure, that I initialized that field on another way.

Related

How to deserialize a JSON file ( using Google JSON) consisting of same key name but uses different type?

Consider the following JSON File:
{
"version": "1.0",
"firstData": {
"meta": "this is string",
"version": "1"
},
"SecondData": {
"meta": ["string1", "string2", "string3"],
"version": "1"
},
"ThirdData": {
"meta": true,
"version": "1"
},
"FourthData": {
"meta": [true, false, false, true],
"version": "1"
},
"FifthData": {
"meta": [{
"meta": "string",
"version": "2"
},
{
"meta": ["string1","string2"],
"version": "2"
}]
"version": "1"
}
}
As seen, The "meta" attribute has different data type, sometimes it is String, sometimes it is ArrayOfString, sometimes Boolean etc.
Since my JSON file has several data,
I want it to follow the following Structure :
class information
{
String version;
HashMap<String,Data> details;
}
class Data
{
variable meta;
String version;
}
How do I create a corresponding POJO and deserialize it using Google GSON?
Just define your meta as JsonElement. Then you will have sort methods like: getAsString, getAsBoolean, getAsJsonObject, getAsJsonArray, ..., and also you are able to deserialize it again after you find out what is the type.
So your class could look like:
public class SomeClass {
private int version;
private JsonElement meta;
//getters and setters and other stuff
}
Edit: More elaboration and implementation
Define two classes: GeneralItem and GeneralData
class GeneralItem
{
public final int version;
public final JsonElement meta;
}
class GeneralData
{
public final String version;
public final Map<String, GeneralItem> items;
public GeneralData(String version, Map<String, GeneralItem> items)
{
this.version = version;
this.items = items;
}
}
And then we define a custom deserializer for our GeneralData:
class GeneralDataDeserializer implements JsonDeserializer<GeneralData>
{
#Override
public GeneralData deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException
{
final JsonObject object = json.getAsJsonObject();
final String version = object.get("version").getAsString();
object.remove("version");
HashMap<String, GeneralItem> items = new HashMap<>(object.size());
for (Map.Entry<String, JsonElement> item : object.entrySet())
items.put(item.getKey(), context.deserialize(item.getValue(), GeneralItem.class));
return new GeneralData(version, items);
}
}
Finally registering the deserializer to our gson instance and getting the data:
final Gson gson = new GsonBuilder()
.registerTypeAdapter(GeneralData.class, new GeneralDataDeserializer())
.create();
final String json = "your json here";
final GeneralData data = gson.fromJson(json, GeneralData.class);
System.out.println(data.items.get("firstData").meta.getAsString());
//other parts you want
(Note that constructors, getter and setters, error checking, etc. are removed for the sake of brevity)

Howto configure which POJO fields to serialize to JSON with GSON?

I have a List of Objects with multiple fields. Based on the API call, the List would be returned with only a specific set of fields. When I use transient - it does not serialize that particular field. But, that field should be returned for another API call. I am using Gson.
In the example below, based on the API, I wanted to print a list of Table instances with only E.g. "name" of the Table instances, or both "name" and "location", or just location. There could be 30 fields in the Table Object.
One way is to map it to a POJO for each scenario and then print it out. Is there a better way to do this? where you can select/choose/constrain which field gets serialized.
E.g.
package Testing;
import java.util.ArrayList;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
public class TestJson {
public static Gson obGson = new GsonBuilder().setPrettyPrinting().create();
public static void main(String[] args) {
ArrayList<Table> myTable = new ArrayList<Table>();
myTable.add(new Table("John", "Chicago"));
myTable.add(new Table("David", "Seattle"));
myTable.add(new Table("June", "Dallas"));
System.out.println(obGson.toJson(myTable));
}
}
class Table {
String name;
String location;
public Table (String _name, String _location) {
name = _name;
location = _location;
}
}
The output for the above looks like this. When API-1 is called then the output should like below.
[
{
"name": "John",
"location": "Chicago"
},
{
"name": "David",
"location": "Seattle"
},
{
"name": "June",
"location": "Dallas"
}
]
But when API-2 is called then the output should like below. Only return the fields that are approved for that API call.
[
{
"name": "John"
},
{
"name": "David"
},
{
"name": "June"
}
]
Similarly, the return could be managed based on the API call.
Implement ExclusionStrategy like
#RequiredArgsConstructor
public class FieldExclusionStrategy implements ExclusionStrategy {
#NonNull
private final Collection<String> serializedFields;
#Override
public boolean shouldSkipField(FieldAttributes f) {
if(serializedFields.contains(f.getName())) return false;
return true;
}
#Override
public boolean shouldSkipClass(Class<?> clazz) { return false; }
}
Use like
#Test
public void testShouldSkipField() {
Gson gson;
Table table = new Table();
Collection<String> serializedFields = new ArrayList<>();
ArrayList<Table> myTable = new ArrayList<Table>();
myTable.add(new Table("John", "Chicago"));
myTable.add(new Table("David", "Seattle"));
myTable.add(new Table("June", "Dallas"));
serializedFields.add("name");
gson = new GsonBuilder()
.setPrettyPrinting()
.addSerializationExclusionStrategy(
new FieldExclusionStrategy(serializedFields))
.create();
log.info("\n{}", gson.toJson(myTable));
serializedFields.add("location");
gson = new GsonBuilder()
.setPrettyPrinting()
.addSerializationExclusionStrategy(
new FieldExclusionStrategy(serializedFields))
.create();
log.error("\n{}", gson.toJson(myTable));
serializedFields.remove("name");
gson = new GsonBuilder()
.setPrettyPrinting()
.addSerializationExclusionStrategy(
new FieldExclusionStrategy(serializedFields))
.create();
log.error("\n{}", gson.toJson(myTable));
}
Above would log something like
2017-12-23 19:47:17.028 INFO org.example.gson.FieldExclusionStrategyTest:37 -
[
{
"name": "John"
},
{
"name": "David"
},
{
"name": "June"
}
]
2017-12-23 19:47:17.034 ERROR org.example.gson.FieldExclusionStrategyTest:44 -
[
{
"name": "John",
"location": "Chicago"
},
{
"name": "David",
"location": "Seattle"
},
{
"name": "June",
"location": "Dallas"
}
]
2017-12-23 19:47:17.035 ERROR org.example.gson.FieldExclusionStrategyTest:51 -
[
{
"location": "Chicago"
},
{
"location": "Seattle"
},
{
"location": "Dallas"
}
]
You need to build GSON again after changing list of serialized field names.
GSON caches internally the result - true|false - upon first call for some field name and does not query it again for cached field name.
And to add ExclusionStrategy you need to build GSON with GsonBuilder which then registers ExclusionStrategy (or many of them).
See also my question about this topic.

How to parse dynamic json in android with retrofit 2 using annotations

I have a JSON structure which I want to parse using retrofit 2 (#Expose). Below I have mentioned the JSON. Need help to parse it using dynamic annotations.
{
"status": 1,
"message": "success",
"data" : [
{
"type": 1,
"heading": "",
"description": "",
"created_on": 141123213,
"author_id": 123,
"author_name": "some name",
"author_pic": "some_pic",
"read_time": "3.1 min",
"post_pic_url": "",
"post_web_url": "",
"isLiked": false,
"isSaved": false,
"totalLikes": 12
},
{
"type": 2,
"author_id": 123,
"author_name": "some name",
"author_pic": "some pic",
"author_about": "",
"tags":[
"travel", "weekends"
],
"isFollowing": false
},
{
"type": 3,
"poll_name": "Some name",
"poll_options": [
"opt1", "opt2", "opt3"
],
"author_id": 123,
"author_name": "some name",
"author_pic": "some pic",
"isLiked": true,
"isFollowing": false
},
{
"type": 4,
"ad_url": "url",
"ad_pic": "pic"
},
{
"type": 5,
"tags": [
"tag1", "tag2", "tag3"
]
}
]
}
I have updated the JSON structure with all 5 types.
1 Use Retrofit convert
example GSON convert
2 Add com.squareup.retrofit2:converter-gson in gradle file
3 Add converter factory in Retrofit object
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(Ws_Url)
.addConverterFactory(GsonConverterFactory.create())
.client(clientBuilder.build())
.build();
4 Create Model class for Your response
Use below link to generate model class
http://www.jsonschema2pojo.org/
Retrofit does not do serialization and deserialization, but Gson does.
You might want to use RuntimeTypeAdapterFactory from the Google Gson extras package.
It's not published at artifact repositories, and you can simply copy the code to your project.
If type adapters are somewhat complex (as they work with JSON streams), you might find JsonDeserializer<T> easier to use and probably maintain (they work with JSON trees consuming some more memory, but it's the only way to go here anyway).
Define your mappings similar to:
// There might be no the common root, and target lists might be parameterized with Object, but it's up to you
abstract class Element {
final int type = Integer.valueOf(0);
// Since the number of types is really finite, we can define all known types in one place
private Element() {
}
static final class Type1Element
extends Element {
// the rest of properties go here
// Gson does not need constructors, neither we do (at least public ones)
private Type1Element() {
}
}
static final class Type2Element
extends Element {
// the rest of properties go here
private Type2Element() {
}
}
}
final class Response<T> {
final int status = Integer.valueOf(0);
final String message = null;
final T data = null;
}
Now the deserializer itself:
final class ElementJsonDeserializer
implements JsonDeserializer<Element> {
private static final JsonDeserializer<Element> elementJsonDeserializer = new ElementJsonDeserializer();
private ElementJsonDeserializer() {
}
// The deserializer is essentially a singleton, but we hide away this fact making sure that only 1 instance exists
static JsonDeserializer<Element> getElementJsonDeserializer() {
return elementJsonDeserializer;
}
#Override
public Element deserialize(final JsonElement jsonElement, final Type type, final JsonDeserializationContext context)
throws JsonParseException {
final int typeCode = jsonElement.getAsJsonObject().getAsJsonPrimitive("type").getAsInt();
// Simple dispatching here
// RuntimeTypeAdapterFactory basically does the same
switch ( typeCode ) {
case 1:
return context.deserialize(jsonElement, Type1Element.class);
case 2:
return context.deserialize(jsonElement, Type2Element.class);
default:
throw new JsonParseException("Unrecognized type: " + typeCode);
}
}
}
Now get it all working together (response.json is your JSON document resource):
private static final Type type = new TypeToken<Response<List<Element>>>() {
}.getType();
private static final Gson gson = new GsonBuilder()
.registerTypeAdapter(Element.class, getElementJsonDeserializer())
.create();
public static void main(final String... args)
throws IOException {
try ( final JsonReader jsonReader = getPackageResourceJsonReader(Q43802350.class, "response.json") ) {
final Response<List<Element>> response = gson.fromJson(jsonReader, type);
response.data
.stream()
.map(Element::getClass)
.map(Class::getSimpleName)
.forEach(System.out::println);
}
}
Output:
Type1Element
Type2Element
Of course, don't forget to register the gson instance with GsonConverterFactory.create(gson) in your Retrofit builder.

Handling JSON response in Gerenic manner

I have two different Json responses(having different keys) generated out of two different requests :
Response 1 :
{
"response": {
"count": 2,
"programs": [
{
"title": "xyz1",
"desc": "ABCDEF1"
},
{
"title": "xyz2",
"desc": "ABCDEF2"
}
]
}
}
Response 2
{
"response": {
"count": 3,
"shows": [
{
"name": "PQR1",
"desc": "qwerty1"
},
{
"name": "PQR2",
"desc": "qwerty2"
},
{
"name": "PQR3",
"desc": "qwerty3"
}
]
}
}
As we can see the responses contain data with different keys. But Ultimately It could be transformed into (Array of) same Java object like this one:
Program {
String title;
int description;
}
I want to write single parsing logic that handles different key names and return Program list. How to achieve this efficiently?
Is there any library available to conveniently do this ?
You may choose the field in the getter when deserialized both of them (example works with GSON):
class Program {
private String title, name;
#SerializedName("desc") private String description;
private String getTitle() {
return title == null ? name : title;
}
// other getters, empty constructor and so on...
}
Also (again GSON), you can register your own TypeAdapter when creating Gson object.
// let Program have empty constructor (or no constructors at all), getters and setters
class ProgramAdapter extends TypeAdapter<Program> {
#Override
public Program read(final JsonReader in) throws IOException {
final Program obj = new Program();
in.beginObject();
while (in.hasNext()) {
String jsonTag = in.nextName();
if ("desc".equals(jsonTag)) {
obj.setDescription(in.nextString());
} else if ("title".equals(jsonTag)
|| "name".equals(jsonTag)) {
obj.setTitle(in.nextString());
}
}
in.endObject();
return obj;
}
#Override
public void write(final JsonWriter out, final Program obj)
throws IOException {
out.beginObject();
out.name("title").value(obj.getTitle());
out.name("desc").value(obj.getDescription());
out.endObject();
}
}
// then, when create `Gson` object:
Gson gson = new GsonBuilder().registerTypeAdapter(Program.class, new ProgramAdapter()).create();

"no suitable constructor" error when deserializing JSON children

I'm trying to map a json structure to a pojo using fasterxml/jackson.
My json comes from a file and looks like this:
{
"groups": [
{
"name": "Group1",
"icon": "group1.png",
"banner": "banner.png"
},
{
"name": "Group2",
"icon": "group2.png",
"banner": "banner.png"
}
],
"ticker": [
{
"title": "ticker1",
"description": "description",
"url": "bla",
"icon": "icon.png",
"banner": "banner.png",
"group": "Group1",
"enabled": "true",
"startTime": "00:00",
"startDate": "15.10.2013"
}
]
}
I'm interested in the groups. Therefore I created a class Groups:
public class Groups implements Serializable {
private final static long serialVersionUID = 42L;
private List<Group> groups;
public Groups() {}
public Groups ( List<Group> groups ) {
this.groups = groups;
}
public List<Group> getGroups() {
if (groups == null) {
groups = new ArrayList<Group>();
}
return groups;
}
public void add(Group group) {
getGroups().add(group);
}
}
Usually I am using this code to map a json to a pojo:
public static <T> T readJsonFile(File file, Class<T> valueType) throws IOException {
String json = readJsonFile(file.getAbsolutePath());
if( StringUtils.isEmpty(json) ) {
return null;
}
return createObjectMapper().readValue(json, valueType);
}
This works fine if the pojo is the outer json object.
But if I am trying to extract the groups it fails with:
"no suitable constructor".
How is it possible to extract a pojo that is nested in a json structure?
public Groups() {
groups = new ArrayList<>();
}
The default constructor is used on serialization, and groups is just defined as interface.
I would even change all, and initialize the field to a non-null value.

Categories

Resources