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

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.

Related

How to load json data in java?

I'm quite new to API testing, I am wondering how to best and simple load some body?
I created simple pojo classes, but i am having problems with nested json.
ex:
{
"listOfItems": [
{
"name": "name1",
"value": "Jack"
},
{
"name": "nameDate",
"value": "20-08-2021-08-00-00"
},
{
"name": "address",
"value": "address here",
}
{
"name": "name2",
"value": "Smith"
}
],
"something": [],
"size": 1
}
Then, in classes I used:
ClassName {
private List<ListOfItems> listOfItems;
private List<something> something;
private int size;
//setters and getters
}
and
Class ListOfItems{
private String name;
private String value;
//getters and setters
}
then in test class I am trying to use it, but have no idea how.
public Class Test {
ClassName className = new ClassName();
ListOfItems list = new ListOfItems();
//how to get list with 3x name and 3x value like in json?
className.setsize(150);
given().when().body(???).post("\endpoint").then()...
}
But I have no idea how to declare those 4 properties (name, value)
You are actually on a pretty good track, you can use Gson library to help you out, Here is the video example for your explanation which I used to learn Gson back when I needed it

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.

Write java class with nested Map to custom json format using Gson

What Java data structure can be used to serialize to the following Json which have a set of child objects?
Example:
{
"John": {
"Type": "Person",
"age": 30,
"Sean": {
"Type": "child",
"age": 3
},
"Julian": {
"Type": "child",
"age": 4
}
},
"Paul": {
"Type": "Person",
"age": 64,
"Stella": {
"Type": "child",
"age": 10
},
"James": {
"Type": "child",
"age": 12
}
}
}
Writing John and Paul can be done by: Map<String,Person> but i cannot figure out how to nest the Child without having the 'children' property.
Example:
{
"John": {
"Type": "Person",
"age": 30,
"children": {
"Sean": {
"Type": "child",
"age": 3
},
"Julian": {
"Type": "child",
"age": 4
}
}
}
}
I am not sure it is relevant, but Gson is being used to create the Json file
I'm not sure wheher this is possible with GSON though it's possible with Jackson.
With GSON you can try custom JsonSerializer, which might look something like this:
private static class PersonTypeSerializer implements JsonSerializer<Person> {
#Override
public JsonElement serialize(Person person, Type type, JsonSerializationContext jsonSerializationContext) {
JsonObject personJson = personToJson(person);
for (Map.Entry<String, Person> child : person.getChildren().entrySet()) {
personJson.add(child.getKey(), personToJson(child.getValue()));
}
return personJson;
}
private static JsonObject personToJson(Person person) {
JsonObject personJson = new JsonObject();
personJson.addProperty("Type", person.getType());
personJson.addProperty("age", person.getAge());
return personJson;
}
}
and register to GSON instance like this:
Gson gson = new GsonBuilder().registerTypeAdapter(Person.class, new PersonTypeSerializer())
.create();
Note that code assumes that both type "Person" and "child" are represented by the same Person class. It should be pretty easy to modify this if not.

"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.

Fill a Java Class with GSon Builder and JSon File

I want to fill a Java Class with an external JSon File and I works with Gson Builder. But my Java class won´t be filled by the value, which i have declared in the JSon File. What is the problem? Thanks for helping !
This is my Json File:
{
"tstamp": "2012-11-21 18:00",
"id": 123,
"publicationList": [
{
"id": "1L",
"state": "PublicationState.IMMEDIATELY",
"channel": "PublicationChannel.TIMETABLE",
"startDate": "2001-12-17 18:00",
"endDate": "2001-12-17 18:00"
},
{
"id": "2L",
"state": "PublicationState.IMMEDIATELY",
"channel": "PublicationChannel.NVS",
"startDate": "2001-12-17 18:00",
"endDate": "2001-12-17 18:00"
},
{
"id": "3L",
"state": "PublicationState.IMMEDIATELY",
"channel": "PublicationChannel.NVS",
"startDate": "2001-12-17 18:00",
"endDate": "2001-12-17 18:00"
}
]
}
This is my Gson Builder and GSon Instance Creator:
Gson gson = new GsonBuilder()
.setDateFormat("yyyy-MM-dd HH:mm")
.registerTypeAdapter(Publication.class,
new PublicationInstanceCreator()).create();
public class PublicationInstanceCreator implements
InstanceCreator<PublicationDto> {
#Override
public PublicationDto createInstance(Type type) {
return new PublicationDto();
}
}
This is the Java Class PublicattionDto:
public class PublicationDto{
private static final long serialVersionUID = 1L;
private Long id;
private PublicationState state = PublicationState.IMMEDIATELY;
private PublicationChannel channel;
private Date startDate;
private Date endDate;
//getter and setter methods
}
What is your error ? The main POJO object is also missing in your code.
Assuming this object is Publications, you only have to use
Publications p = new GsonBuilder().setDateFormat("yyyy-MM-dd HH:mm").create().fromJson(json, Publications.class);
Your adapter won't work. Plus I think the IDs with 'L' won't work too (drop the L), GSon will cast the number into the correct Long type.

Categories

Resources