"no suitable constructor" error when deserializing JSON children - java

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.

Related

GSON serialization fails with custom Objects

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.

How to access jOOQ generated Routine's field as value by using Custom Converter?

I'm having an issue with accessing field of generated Routine from PL/pgSQL user-defined function (which returns JSON data type as a result) already mentioned in this question.
This is my result of get_all_orders() function created in PL/pgSQL of JSON type:
{
"orders": [
{
"order_id": 1,
"total_price": 29.99,
"order_date": "2019-08-22T10:06:33",
"user": {
"user_id": 1,
"username": "test"
},
"order_items": [
{
"order_item_id": 1,
"amount": 1,
"book": {
"book_id": 1,
"title": "Harry Potter and the Philosopher's Stone",
"price": 29.99,
"amount": 400,
"is_deleted": false,
"authors": [
{
"author_id": 4,
"first_name": "JK",
"last_name": "Rowling"
}
],
"categories": [
{
"category_id": 2,
"name": "Lyric",
"is_deleted": false
}
]
},
"order_id": 1,
"total_order_item_price": 29.99
}
]
},
{
"order_id": 2,
"total_price": 29.99,
"order_date": "2019-08-22T10:10:13",
"user": {
"user_id": 1,
"username": "test"
},
"order_items": [
{
"order_item_id": 2,
"amount": 1,
"book": {
"book_id": 1,
"title": "Harry Potter and the Philosopher's Stone",
"price": 29.99,
"amount": 400,
"is_deleted": false,
"authors": [
{
"author_id": 4,
"first_name": "JK",
"last_name": "Rowling"
}
],
"categories": [
{
"category_id": 2,
"name": "Lyric",
"is_deleted": false
}
]
},
"order_id": 2,
"total_order_item_price": 29.99
}
]
}
]
}
I am trying to access Routine as a Field by following Custom data type bindings manual and what I've managed to do so far is to create custom Converter in order to convert org.jooq.JSON into io.vertx.core.json.JsonObject:
public class JSONJsonObjectConverter implements Converter<JSON, JsonObject>{
private static final long serialVersionUID = -4773701755042752633L;
#Override
public JsonObject from(JSON jooqJson) {
String strVal = (jooqJson == null ? null : jooqJson.toString());
return strVal == null ? null : JsonObject.mapFrom(strVal);
}
#Override
public JSON to(JsonObject vertxJson) {
String strVal = (vertxJson == null ? null : vertxJson.toString());
return strVal == null ? null : JSON.valueOf(strVal);
}
#Override
public Class<JSON> fromType() {
return JSON.class;
}
#Override
public Class<JsonObject> toType() {
return JsonObject.class;
}
}
...this is link to QueryResult soruce code and I'm using this method to invoke it (Custom created Converter):
public static JsonObject convertGetAllOrdersQRToJsonObject(QueryResult qr) {
//JsonArray ordersJA = qr.get("orders", JsonArray.class);
DataType<JsonObject> jsonObjectType = SQLDataType.JSON.asConvertedDataType(new JSONJsonObjectConverter());
//DataType<JsonArray> jsonArrayType = SQLDataType.JSONArray.asConvertedDataType(new JsonArrayConverter());
DataType<JsonObject> jsonObjectTypeDefault = SQLDataType.JSON.asConvertedDataType((Binding<? super JSON, JsonObject>) new JsonObjectConverter());
Field<JsonObject> ordersFieldDefault = DSL.field("get_all_orders", jsonObjectTypeDefault);
Field<JsonObject> ordersField = DSL.field("get_all_orders", jsonObjectType);
JsonObject orders = qr.get("orders", JsonObject.class);
// return new JsonObject().put("orders", orders);
return new JsonObject().put("orders", ordersField); // try ordersFieldDefault(.toString()) as value parameter
}
I'm invoking above mentioned methods inside of following one:
Future<QueryResult> ordersFuture = queryExecutor.transaction(qe -> qe
.query(dsl -> dsl
.select(Routines.getAllOrders())
));
LOGGER.info("Passed ordersFuture...");
ordersFuture.onComplete(handler -> {
if (handler.succeeded()) {
QueryResult qRes = handler.result();
JsonObject ordersJsonObject = OrderUtilHelper.convertGetAllOrdersQRToJsonObject(qRes);
LOGGER.info("ordersJsonObject.encodePrettily(): " + ordersJsonObject.encodePrettily());
resultHandler.handle(Future.succeededFuture(ordersJsonObject));
} else {
LOGGER.error("Error, something failed in retrivening ALL orders! handler.cause() = " + handler.cause());
queryExecutor.rollback();
resultHandler.handle(Future.failedFuture(handler.cause()));
}
});
...and this is generated method in Routines.java class which is being used in last mentioned code above in expression that returns value into dsl -> dsl.select(Routines.getAllOrders()) statement part:
/**
* Convenience access to all stored procedures and functions in public
*/
#SuppressWarnings({ "all", "unchecked", "rawtypes" })
public class Routines {
/**
* Get <code>public.get_all_orders</code> as a field.
*/
public static Field<JSON> getAllOrders() {
GetAllOrders f = new GetAllOrders();
return f.asField();
}
}
...and (finally) here is my *.jooq.routines.GetAllOrders.java class:
/**
* This class is generated by jOOQ.
*/
#SuppressWarnings({ "all", "unchecked", "rawtypes" })
public class GetAllOrders extends AbstractRoutine<JSON> {
private static final long serialVersionUID = 917599810;
/**
* The parameter <code>public.get_all_orders.RETURN_VALUE</code>.
*/
public static final Parameter<JSON> RETURN_VALUE = Internal.createParameter("RETURN_VALUE", org.jooq.impl.SQLDataType.JSON, false, false);
/**
* Create a new routine call instance
*/
public GetAllOrders() {
super("get_all_orders", Public.PUBLIC, org.jooq.impl.SQLDataType.JSON);
setReturnParameter(RETURN_VALUE);
}
}
BTW, Vertx library also uses JsonArray class which is used to work with arrays but I don't see a way to map ALREADY generated org.jooq.JSON into org.jooq.impl.JSONArray and then into io.vertx.core.json.JsonArray type.
Is there I am missing something (I know I am dealing with generated Routine, but example given in jOOQ's manual contains only Table field)...or maybe I should've created Custom Data Type Binding class?
Any suggestion/help is greatly appreciated.
UPDATE1:
I've followed instructions given in linked Q&A in comments and this is what I've added and have already of <forcedType>s in my pom.xml :
<!-- Convert varchar column with name 'someJsonObject' to a io.vertx.core.json.JsonObject -->
<forcedType>
<userType>io.vertx.core.json.JsonObject</userType>
<converter>io.github.jklingsporn.vertx.jooq.shared.JsonObjectConverter</converter>
<includeExpression>someJsonObject</includeExpression>
<includeTypes>.*</includeTypes>
<nullability>ALL</nullability>
<objectType>ALL</objectType>
</forcedType>
<!-- Convert varchar column with name 'someJsonArray' to a io.vertx.core.json.JsonArray -->
<forcedType>
<userType>io.vertx.core.json.JsonArray</userType>
<converter>
io.github.jklingsporn.vertx.jooq.shared.JsonArrayConverter</converter>
<includeExpression>someJsonArray</includeExpression>
<includeTypes>.*</includeTypes>
<nullability>ALL</nullability>
<objectType>ALL</objectType>
</forcedType>
<forcedType>
<userType>io.vertx.core.json.JsonObject</userType>>
<!-- also tried to use "org.jooq.Converter.ofNullable(Integer.class, String.class, Object::toString, Integer::valueOf)"
and it did NOT work so I gave this custom created Conveter a try and it ALSO did NOT work! -->
<converter>
com.ns.vertx.pg.converters.JSONJsonObjectConverter.ofNullable(JSON.class, JsonObject.class, JsonObject::toString, JSON::valueOf)
</converter>
<includeExpression>(?i:get_all_orders|return_value)</includeExpressio>
</forcedType>
...and when I do Maven> Update Project + check Force Update of Snapshots/Releases I get 32 overall following ERROR messages:
JsonObject cannot be resolved
...and
JsonObject cannot be resolved to a type
...and this is my generated *.jooq.routines.GetAllOrders.java class:
// This class is generated by jOOQ.
#SuppressWarnings({ "all", "unchecked", "rawtypes" })
public class GetAllOrders extends AbstractRoutine<JsonObject> {
private static final long serialVersionUID = -431575258;
// The parameter <code>public.get_all_orders.RETURN_VALUE</code>.
public static final Parameter<JsonObject> RETURN_VALUE = Internal.createParameter("RETURN_VALUE", org.jooq.impl.SQLDataType.JSON, false, false, org.jooq.Converter.ofNullable(JSON.class, JsonObject.class, JsonObject::toString, JSON::valueOf));
//Create a new routine call instance
public GetAllOrders() {
super("get_all_orders", Public.PUBLIC, org.jooq.impl.SQLDataType.JSON, org.jooq.Converter.ofNullable(JSON.class, JsonObject.class, JsonObject::toString, JSON::valueOf));
setReturnParameter(RETURN_VALUE);
}
}
I already have these programmaticly created converter for this generator ClassicReactiveVertxGenerator (more info about it avaliable here) for io.vertx.core.json.JsonObject in 1st mentioned <forcedType>. Any suggestion how to resolve this issue?
UPDATE2:
I've also tried to employ this org.jooq.Converter converter like this (had to use qualified refernce for JSON class otherwise it didn't perform imports in Generated Routine clases):
<forcedType>
<userType>java.lang.String</userType>
<converter>
org.jooq.Converter.ofNullable(org.jooq.JSON.class, String.class, Object::toString,org.jooq.JSON.class::valueOf)
</converter>
<includeExpression>(?i:get_all_orders|return_value) </includeExpression>
</forcedType>
...and I get this in generated GetAllOrders.java class:
#SuppressWarnings({ "all", "unchecked", "rawtypes" })
public class GetAllOrders extends AbstractRoutine<String> {
private static final long serialVersionUID = 1922028137;
// The parameter <code>public.get_all_orders.RETURN_VALUE</code>.
public static final Parameter<String> RETURN_VALUE = Internal.createParameter("RETURN_VALUE", org.jooq.impl.SQLDataType.JSON, false, false, org.jooq.Converter.ofNullable(org.jooq.JSON.class, String.class, Object::toString, org.jooq.JSON.class::valueOf));
// in above value I get ERROR "The method ofNullable(Class<T>, Class<U>, Function<? super T,? extends U>, Function<? super U,? extends T>) in the type Converter is not applicable for the arguments (Class<JSON>, Class<String>, Object::toString, org.jooq.JSON.class::valueOf)"
// ... for org.jooq.Converter.ofNullable(..) method + 23 same/similar ERRORS
// Create a new routine call instance
public GetAllOrders() {
super("get_all_orders", Public.PUBLIC, org.jooq.impl.SQLDataType.JSON, org.jooq.Converter.ofNullable(org.jooq.JSON.class, String.class, Object::toString, org.jooq.JSON.class::valueOf));
setReturnParameter(RETURN_VALUE);
}
}
Since that did NOT work, I've tried to solve that problem by creating JooqJsonConverter.java Custom Converter class like this:
public class JooqJsonConverter implements Converter<String, JSON>{
private static final long serialVersionUID = -4773701755042752633L;
#Override
public JSON from(String jooqJson) { return jooqJson == null ? null : JSON.valueOf(jooqJson); }
#Override
public String to(JSON jooqJson) { return jooqJson == null ? null : jooqJson.toString(); }
#Override
public Class<String> fromType() { return String.class; }
#Override
public Class<JSON> toType() { return JSON.class; }
}
...and changing Converter under tag:
<converter>
com.ns.vertx.pg.converters.JooqJsonConverter.ofNullable(org.jooq.JSON.class, String.class, Object::toString,org.jooq.JSON.class::valueOf)
</converter>
...and I get same code GetAllOrders.java class with diffrence small difference
public static final Parameter<String> RETURN_VALUE = Internal.createParameter("RETURN_VALUE", org.jooq.impl.SQLDataType.JSON, false, false, com.ns.vertx.pg.converters.JooqJsonConverter.ofNullable(org.jooq.JSON.class, String.class, Object::toString, org.jooq.JSON.class::valueOf));
public GetAllOrders() {
super("get_all_orders", Public.PUBLIC, org.jooq.impl.SQLDataType.JSON, com.ns.vertx.pg.converters.JooqJsonConverter.ofNullable(org.jooq.JSON.class, String.class, Object::toString, org.jooq.JSON.class::valueOf));
setReturnParameter(RETURN_VALUE);
}
...and ONLY this 8 errors (2 for each of 4 generated Routine classes):
The method ofNullable(Class, Class, Object::toString,
org.jooq.JSON.class::valueOf) is undefined for the type
JooqJsonConverter
Any idea what is missing/am I doing wrong?
Thank you in advance.
The first part of your question is answered here. In your updates, this is not valid Java code:
org.jooq.JSON.class::valueOf
You meant to write this:
org.jooq.JSON::valueOf

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.

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();

Categories

Resources