I am writing a Java library for interacting with metrics from Graphite.
A typical JSON response looks like this (taken from the official docs):
[{
"target": "entries",
"datapoints": [
[1.0, 1311836008],
[2.0, 1311836009],
[3.0, 1311836010],
[5.0, 1311836011],
[6.0, 1311836012]
]
}]
where the first element of the "datapoints" array is the value and the second one the timestamp. I have modelled a GraphiteDataset class as follows
class GraphiteDataset {
private String target;
private List<GraphiteDatapoint> datapoints;
....
}
and the GraphiteDatapoint class
class GraphiteDatapoint {
private Long timestamp;
private Double value;
...
}
Now I need to parse the response (see above) into the GraphiteDataset
class using Gson. Unfortunately, the elements of "datapoints" are not named objects (e.g. {timestamp: 1234, value: 1.0} but a 2 dimensional array so I cannot directly deserialize it into some class. Currently my solution is to have an intermediate class
class GraphiteIntermediateDataset {
private String target;
private List<String> datapoints;
...
}
which has the datapoints as Strings and then I parse them into the appropriate GraphiteDatapoint instance. I think that I cannot work around a custom deserializer. Do you have any suggestions or tricks how to make this a little more convenient?
The JSON [1.2, 123456] is a array of a Double and a Long, but they are both Number, so try this:
class GraphiteDataset {
private String target;
private List<List<Number>> datapoints;
....
}
Then convert datapoints into your type after parsing, with something like:
List<GraphiteDatapoint> points = datapoints.stream().
.map(nums -> new GraphiteDatapoint(nums.get(0).doubleValue(), nums.get(1).intValue()))
.collect(Collectors.toList());
assuming a constructor like:
class GraphiteDatapoint {
private Long timestamp;
private Double value;
public GraphiteDatapoint(Double value, Long timestamp) {
this.value = value;
this.timestamp = timestamp;
}
...
}
The final solution is to introduce an intermediate class GraphiteIntermediateDataset which looks as follows:
class GraphiteIntermediateDataset {
private String target;
private List<List<Number>> datapoints;
}
and the deserializer code looks like this
List<GraphiteIntermediateDataset> intermediateDatasetList = GSON.fromJson(raw, new TypeToken<List<GraphiteIntermediateDataset>>(){}.getType());
GraphiteIntermediateDataset intermediateDataset = intermediateDatasetList.get(0);
... check if empty (which can happen), when true return an empty GraphiteDataset
List<GraphiteDatapoint> gDatapoints = intermediateDataset
.stream()
.map(ds -> {
return new GraphiteDatapoint(ds.get(0).longValue(),
ds.get(1).doubleValue())
}
.collect(Collectors.toList());
return new GraphiteDataset()
.setDatapoints(gDatapoints);
Type safety and proper data binding are your friends. Gson has several methods to accomplish what you need. For example, declare data transfer objects:
final class GraphiteDataset {
final String target;
// The incoming DTO has property `datapoints`, however Java conventions suggest dataPoints (as far as I understand English).
#SerializedName("datapoints")
final List<GraphiteDataPoint> dataPoints;
// Actually, Gson does not need this constructor, and the DTO can even have a single private default one.
// But in order to make it consistent with the next class just making it programmatically instantiable...
// Also, but may be opinion-based, hiding constructors is really a good idea since one can hide the instantiation strategy whilst constructors cannot.
private GraphiteDataset(final String target, final List<GraphiteDataPoint> dataPoints) {
this.target = target;
this.dataPoints = dataPoints;
}
}
final class GraphiteDataPoint {
final double value;
final long timestamp;
private GraphiteDataPoint(final double value, final long timestamp) {
this.value = value;
this.timestamp = timestamp;
}
// Instantiation must be accessible programmatically somehow
static GraphiteDataPoint graphiteDataPoint(final double value, final long timestamp) {
return new GraphiteDataPoint(value, timestamp);
}
}
And then implement either a GraphiteDataPoint JSON deserializer:
// In Gson serializers and deserializers can only deal with intermediate Gson JSON tree representation of objects (JsonElement-s).
// For some cases it's quite simple, if the given data to serialize/deserialize does not consume much memory
final class GraphiteDataPointJsonDeserializer
implements JsonDeserializer<GraphiteDataPoint> {
private static final JsonDeserializer<GraphiteDataPoint> graphiteDataPointJsonDeserializer = new GraphiteDataPointJsonDeserializer();
private GraphiteDataPointJsonDeserializer() {
}
// Not letting to instantiate a stateless (so it's thread-safe) deserializer twice or more
static JsonDeserializer<GraphiteDataPoint> getGraphiteDataPointJsonDeserializer() {
return graphiteDataPointJsonDeserializer;
}
#Override
public GraphiteDataPoint deserialize(final JsonElement jsonElement, final Type type, final JsonDeserializationContext context)
throws JsonParseException {
final JsonArray asJsonArray = jsonElement.getAsJsonArray();
final double value = asJsonArray.get(0).getAsJsonPrimitive().getAsDouble();
final long timestamp = asJsonArray.get(1).getAsJsonPrimitive().getAsLong();
return graphiteDataPoint(value, timestamp);
}
}
Or a type adapter:
// Type adapters, unlike serializers and deserializers, are designed to work with streams.
// They may look too low-level and tedious/hard to implement, but for some cases they can be useful in both serialization and deserialization.
// For the case #1: no need to serialize nested objects recursively to transform them to JSON trees that can be important for large objects.
// For the case #2: intermediate JSON trees are not necessary (but internal buffers are).
final class GraphiteDataPointTypeAdapter
extends TypeAdapter<GraphiteDataPoint> {
private static final TypeAdapter<GraphiteDataPoint> graphiteDataPointTypeAdapter = new GraphiteDataPointTypeAdapter();
private GraphiteDataPointTypeAdapter() {
}
static TypeAdapter<GraphiteDataPoint> getGraphiteDataPointTypeAdapter() {
return graphiteDataPointTypeAdapter;
}
#Override
public void write(final JsonWriter out, final GraphiteDataPoint value) {
throw new UnsupportedOperationException("not implemented");
}
#Override
public GraphiteDataPoint read(final JsonReader in)
throws IOException {
in.beginArray();
final double value = in.nextDouble();
final long timestamp = in.nextLong();
in.endArray();
return graphiteDataPoint(value, timestamp);
}
}
Both implementations are essentially the same, but may be crucial for you dependening on data (de)serialization strategies and costs. Example use:
private static final String JSON = "[{\"target\":\"entries\",\"datapoints\":[[1.0,1311836008],[2.0,1311836009],[3.0,1311836010],[5.0,1311836011],[6.0,1311836012]]}]";
// Gson is thread-safe and can be shared between threads, so no need to instantiate it every time it's needed
private static final Gson gsonWithDeserializers = new GsonBuilder()
.registerTypeAdapter(GraphiteDataPoint.class, getGraphiteDataPointJsonDeserializer())
.create();
private static final Gson gsonWithTypeAdapters = new GsonBuilder()
.registerTypeAdapter(GraphiteDataPoint.class, getGraphiteDataPointTypeAdapter())
.create();
private static final TypeToken<List<GraphiteDataset>> graphiteDatasetsTypeToken = new TypeToken<List<GraphiteDataset>>() {
};
public static void main(final String... args) {
dumpGraphiteDatasets(gsonWithDeserializers.fromJson(JSON, graphiteDatasetsTypeToken.getType()));
dumpGraphiteDatasets(gsonWithTypeAdapters.fromJson(JSON, graphiteDatasetsTypeToken.getType()));
}
private static void dumpGraphiteDatasets(final Iterable<GraphiteDataset> graphiteDatasets) {
graphiteDatasets.forEach(graphiteDataset -> {
out.println(graphiteDataset.target);
graphiteDataset.dataPoints.forEach(graphiteDataPoint -> {
out.print(" ");
out.print(graphiteDataPoint.value);
out.print(" ");
out.println(graphiteDataPoint.timestamp);
});
});
}
The output:
entries
1.0 1311836008
2.0 1311836009
3.0 1311836010
5.0 1311836011
6.0 1311836012
entries
1.0 1311836008
2.0 1311836009
3.0 1311836010
5.0 1311836011
6.0 1311836012
Related
How to configure Gson to do additional processing on the value for toJson?
public class MyClass{
#SerializedName("qwerty")
#Mask(exposeFront=2, exposeRear=2, mask="*")
private String qwerty
}
Assuming MyClass#qwerty has a value of 1234567890, how to set Gson to output {"qwerty":"12******90"}?
Gson ReflectiveTypeAdapterFactory, that is responsible for "plain" objects serialization and deserialization, is not possible to enhance to support any other annotations like #Masked. It can only use annotations like #Expose (indirectly via an exclusion strategy), #SerializedName and a few others like #Since and #Until (exclusion strategy too). Note these annotations are documented and supported by default. In general, Gson suggests using a type adapter for the declaring class, MyClass, but this also means that you must manage all fields and make sure the corresponding type adapter is updated once your class is changed. Even worse, adding a custom type adapter makes these annotations support lost.
As an another way of working around it is injecting a special string type adapter factory that can do the trick, but due to the mechanics of how it is injected, this is both limited and requires duplicating the #Masked annotation values (if you're using the annotation elsewhere in your code) and the type adapter factory configuration in #JsonAdapter.
public abstract class MaskedTypeAdapterFactory
implements TypeAdapterFactory {
private final int exposeFront;
private final int exposeRear;
private final char mask;
private MaskedTypeAdapterFactory(final int exposeFront, final int exposeRear, final char mask) {
this.exposeFront = exposeFront;
this.exposeRear = exposeRear;
this.mask = mask;
}
// must be "baked" into the class (name only represents the configuration)
public static final class _2_2_asterisk
extends MaskedTypeAdapterFactory {
private _2_2_asterisk() {
super(2, 2, '*');
}
}
#Override
#Nullable
public <T> TypeAdapter<T> create(final Gson gson, final TypeToken<T> typeToken) {
if ( typeToken.getRawType() != String.class ) {
return null;
}
#SuppressWarnings("unchecked")
final TypeAdapter<String> delegate = (TypeAdapter<String>) gson.getAdapter(typeToken);
final TypeAdapter<String> typeAdapter = new TypeAdapter<String>() {
#Override
public void write(final JsonWriter out, final String value)
throws IOException {
// mask the value
final int length = value.length();
final char[] buffer = value.toCharArray();
for ( int i = exposeFront; i < length - exposeRear; i++ ) {
buffer[i] = mask;
}
out.value(new String(buffer));
}
#Override
public String read(final JsonReader in)
throws IOException {
return delegate.read(in);
}
}
.nullSafe();
#SuppressWarnings("unchecked")
final TypeAdapter<T> adapter = (TypeAdapter<T>) typeAdapter;
return adapter;
}
}
#NoArgsConstructor
#AllArgsConstructor
final class MyClass {
#SerializedName("qwerty")
#Mask(exposeFront = 2, exposeRear = 2, mask = "*")
// unfortunately, this must duplicate the #Mask annotation values
// since type adapter (factories) do not accept supplemental information
// and Java annotations can only accept compile-time constants
#JsonAdapter(MaskedTypeAdapterFactory._2_2_asterisk.class)
#SuppressWarnings("unused")
private String qwerty;
}
Test:
public final class MaskedTypeAdapterFactoryTest {
private static final Gson gson = new GsonBuilder()
.disableHtmlEscaping()
.disableInnerClassSerialization()
.create();
#Test
public void test() {
final String actual = gson.toJson(new MyClass("1234567890"));
final String expected = "{\"qwerty\":\"12******90\"}";
Assertions.assertEquals(expected, actual);
}
}
This is probably the most robust way of doing that in Gson.
I'm trying to process a json file using gson, but I'm running into a weird error. The json I'm reading from (and can't modify) has a weird way of dealing with null fields. It puts an [] in places where there is no data, causing gson to think it's an array when it's expecting a object.
An example from the gson:
//non-empty field
"prizes":[
{
"year":"1902",
"category":"physics",
"share":"2",
"motivation":"\"in recognition of the extraordinary service they rendered by their researches into the influence of magnetism upon radiation phenomena\"",
"affiliations":[
{
"name":"Leiden University",
"city":"Leiden",
"country":"the Netherlands"
}
]
}
]
//empty field
"prizes":[
{
"year":"1903",
"category":"physics",
"share":"4",
"motivation":"\"in recognition of the extraordinary services they have rendered by their joint researches on the radiation phenomena discovered by Professor Henri Becquerel\"",
"affiliations":[
[]
]
}
]
And this is my code for processing the json:
public static void main(String[] args) throws IOException {
// Get Gson object
Gson gson = new Gson();
// read JSON file data as String
String fileData = new
String(Files.readAllBytes(Paths.get("laureates.json")));
// parse json string to object
Example laur = gson.fromJson(fileData, Example.class);
// print object data
System.out.println("\n\nLaureates Object\n\n" + laur);
}
And I have all my classes set up, i believe it will work once this issue is resolved.
The error I'm getting is "Expected BEGIN_OBJECT but was BEGIN_ARRAY at line 1 column 3401" (column 3401 is the exact location of the first [])
The correct way to set the empty object is without the brackets. You know that. :-)
"prizes":[
{
"year":"1903",
"category":"physics",
"share":"4",
"motivation":"\"in recognition of the extraordinary services they have rendered by their joint researches on the radiation phenomena discovered by Professor Henri Becquerel\"",
"affiliations":[
]
}
]
You maybe make a workaround removing the brackets.
fileData = fileData.replaceAll("\\[]", "");
I hope this helps.
Looks like gson is expecting an object but array is returned
Try changing Example to an array as follows.
Example[] emps= gson.fromJson(yourJson, Example
[].class);
Also see related GSON throwing "Expected BEGIN_OBJECT but was BEGIN_ARRAY"?
You can always use a type adapter to adapt bad-designed but well-formed JSON documents. For example, the following type adapter fixes your case:
final class EmptyListFixTypeAdapterFactory
implements TypeAdapterFactory {
private static final TypeAdapterFactory instance = new EmptyListFixTypeAdapterFactory();
private EmptyListFixTypeAdapterFactory() {
}
static TypeAdapterFactory get() {
return instance;
}
#Override
public <T> TypeAdapter<T> create(final Gson gson, final TypeToken<T> typeToken) {
// If it's not a list, then just let Gson pass through the rest of the type adapters chain
if ( !List.class.isAssignableFrom(typeToken.getRawType()) ) {
return null;
}
// Get the original List adapter - we'll use it below
#SuppressWarnings("unchecked")
final TypeAdapter<List<Object>> delegateTypeAdapter = (TypeAdapter<List<Object>>) gson.getDelegateAdapter(this, typeToken);
// Wrap it
#SuppressWarnings("unchecked")
final TypeAdapter<T> typeAdapter = (TypeAdapter<T>) EmptyListFixTypeAdapter.get(delegateTypeAdapter);
return typeAdapter;
}
private static final class EmptyListFixTypeAdapter<E>
extends TypeAdapter<List<E>> {
// JsonParser as of Gson 2.8.2 holds no state
private static final JsonParser jsonParser = new JsonParser();
private final TypeAdapter<List<E>> delegateTypeAdapter;
private EmptyListFixTypeAdapter(final TypeAdapter<List<E>> delegateTypeAdapter) {
this.delegateTypeAdapter = delegateTypeAdapter;
}
private static <E> TypeAdapter<List<E>> get(final TypeAdapter<List<E>> delegateTypeAdapter) {
return new EmptyListFixTypeAdapter<>(delegateTypeAdapter)
.nullSafe(); // A convenient method to add null-checking automatically
}
#Override
public void write(final JsonWriter out, final List<E> value)
throws IOException {
// In case if you need to produce document with this quirks
if ( value.isEmpty() ) {
out.beginArray();
out.beginArray();
out.endArray();
out.endArray();
return;
}
delegateTypeAdapter.write(out, value);
}
#Override
public List<E> read(final JsonReader in) {
final JsonElement jsonElement = jsonParser.parse(in);
final JsonArray array = jsonElement.getAsJsonArray();
// Is it [[]]?
if ( array.size() == 1 ) {
final JsonElement element = array.get(0);
if ( element.isJsonArray() && ((JsonArray) element).size() == 0 ) {
// Yes, detected
return new ArrayList<>();
}
}
// No, proceed with the delegate type adapter
return delegateTypeAdapter.fromJsonTree(array);
}
}
}
Now suppose you have the following mappings:
final class Laureate {
final List<Prize> prizes = new ArrayList<>();
}
final class Prize {
final int year = Integer.valueOf(0);
final String category = null;
final List<Affiliation> affiliations = new ArrayList<>();
}
final class Affiliation {
final String name = null;
final String city = null;
final String country = null;
}
And then:
private static final Gson gson = new GsonBuilder()
.registerTypeAdapterFactory(EmptyListFixTypeAdapterFactory.get())
.create();
private static final Type laureatesType = new TypeToken<List<Laureate>>() {
}.getType();
public static void main(final String... args)
throws IOException {
try ( final JsonReader jsonReader = Resources.getPackageResourceJsonReader(Q49603826.class, "laureates.json") ) {
gson.<List<Laureate>>fromJson(jsonReader, laureatesType)
.stream()
.flatMap(laureate -> laureate.prizes.stream())
.peek(prize -> System.out.println("Prize: " + prize.year + " " + prize.category))
.flatMap(prize -> prize.affiliations.stream())
.peek(affiliation -> System.out.println("\tAffiliation: " + affiliation.name + " " + affiliation.city + " " + affiliation.country))
.forEach(affiliation -> {
});
}
}
Output:
Prize: 1902 physics
........Affiliation: Leiden University Leiden the Netherlands
Prize: 1903 physics
I created a program which contains a JavaFX TableView.
The datasource is a observableArrayList<Users> which is charged with json data. After the table showed the data and call the save procedure by serializing observableArrayList <Utenti> as follows:
Type collectionType = new TypeToken<Collection<Utenti>>(){}.getType();
System.out.println(new Gson().toJson(observableListUsers,collectionType));
the output does a key "helper": {"observable": {}}
I'd like to understand the cause of this behavior and how to prevent that from happening.
this and the original structure:
[{"cognome":{"name":"","value":"ROSSI","valid":true},"nome":{...},"alias":{...}}]
and this is altered
[{"cognome":{"name":"","value":"ROSSI","valid":true,"helper":{"observable":{}}},"nome":{...},"alias":{...}}]
My class Utenti:
package sample.Model;
import javafx.beans.property.SimpleStringProperty;
public class Utenti {
private SimpleStringProperty cognome;
private SimpleStringProperty nome;
private SimpleStringProperty alias;
private SimpleStringProperty mail;
private SimpleStringProperty nominativo;
public Utenti(String cognome, String nome, String alias, String mail) {
this.cognome = new SimpleStringProperty(cognome);
this.nome = new SimpleStringProperty(nome);
this.alias = new SimpleStringProperty(alias);
this.mail = new SimpleStringProperty(mail);
}
public SimpleStringProperty getNome() {
return nome;
}
public SimpleStringProperty getCognome() {
return cognome;
}
public SimpleStringProperty getNominativo() { return nominativo; }
public SimpleStringProperty getAlias() {
return alias;
}
public SimpleStringProperty getMail() {
return mail;
}
public void setCognome(String cognome) {
this.cognome.set(cognome);
}
public void setNome(String nome) {
this.nome.set(nome);
}
public void setAlias(String alias) {
this.alias.set(alias);
}
public void setMail(String mail) {
this.mail.set(mail);
}
Edit: new code for duplicate problem
public class Controller implements Initializable {
#FXML
TableView tw;
#FXML
TableColumn<User, String> colFirstname;
#FXML
TableColumn<User, String> colLastname;
#FXML
TableColumn<User, String> colAlias;
#FXML
TableColumn<User, String> colMail;
#FXML
TextArea taBefore;
#FXML
TextArea taAfter;
#FXML
Button btn;
private ObservableList<User> observableList = FXCollections.observableArrayList();
#Override
public void initialize(URL location, ResourceBundle resources) {
try {
String content = new Scanner(new File("data.json")).useDelimiter("\\Z").next();
JsonObject jsonObject = new JsonParser().parse(content).getAsJsonObject();
JsonArray jsaUsers = (JsonArray) jsonObject.get("users");
jsaUsers.forEach(user -> observableList.add(new Gson().fromJson(user,User.class)));
colFirstname.setCellValueFactory(celldata -> celldata.getValue().getCognome());
colLastname.setCellValueFactory(celldata -> celldata.getValue().getNome());
colAlias.setCellValueFactory(celldata -> celldata.getValue().getAlias());
colMail.setCellValueFactory(celldata -> celldata.getValue().getMail());
tw.setItems(observableList);
taBefore.setText("BEFORE CLICK\n"+new Gson().toJson(observableList));
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
#FXML
void serializeIt(){
taAfter.setText("AFTER CLICK\n"+new Gson().toJson(observableList));
}
}
This happens because Gson works via reflection and walks through all the fields in a particular object in order to generate JSON, and Gson fields iteration strategy applies over the field. In my opinion you might want to create special light-weight classes for your user entity (see Data Transfer Object pattern) and create necessary fields only that could be even annotated with Gson annotations like #SeriailzedName and #Expose. Converting between your entity observable lists and DTO lists is a trivial operation and can be implemented in many ways: Java 8 Streams API, Google Guava, Apache Collections (?), or your custom code after all. In this case you could control the strategy of transformation more precisely at the call site (I mean, where you use it).
Next, Gson is not aware of the ObservableList and its serialized JSON would be fine (since ObservableList is a List and List instances are well-known to Gson) just before deserialization where you'll get ClassCastException where you expect an ObservableList instance (Gson will deserialize it as an ArrayList). Regarding SimpleStringProperty: it looks like reflection plays fine here and the property objects can be deserialized back with success.
If you want not to deal with DTOs, you can configure Gson to work with JavaFx-related stuff.
First off, let's assume you have a simple entity named FooBar:
final class FooBar {
final SimpleStringProperty foo;
final SimpleStringProperty bar;
FooBar(final String foo, final String bar) {
this.foo = new SimpleStringProperty(foo);
this.bar = new SimpleStringProperty(bar);
}
#Override
public String toString() {
return new StringBuilder("FooBar{")
.append("foo=").append(foo)
.append(", bar=").append(bar)
.append('}')
.toString();
}
}
ObservableStringValue and its subclass SimpleStringProperty can have a special type adapter (TypeAdapter<T>) that would deal with the JSON stream forwards and backwards writing and reading your real objects. Note that JsonSerializer<T> and JsonDeserializer<T> are easier to use, but since they two require JSON tree objects in memory to work, type adapters are more efficient, especially if they are easy to implement (they can be very complex though).
final class ObservableStringValueTypeAdapter
extends TypeAdapter<ObservableStringValue> {
private static final TypeAdapter<ObservableStringValue> observableStringValueTypeAdapter = new ObservableStringValueTypeAdapter();
private ObservableStringValueTypeAdapter() {
}
static TypeAdapter<ObservableStringValue> getObservableStringValueTypeAdapter() {
return observableStringValueTypeAdapter;
}
#Override
#SuppressWarnings("resource")
public void write(final JsonWriter out, final ObservableStringValue value)
throws IOException {
out.value(value.get());
}
#Override
public ObservableStringValue read(final JsonReader in)
throws IOException {
return new SimpleStringProperty(in.nextString());
}
}
This type adapter takes any observable string value and writes it as a simple string rather than all the fields an ObservableStringValue instance may have (this is what you're getting with name, value and valid), but always converts strings to SimpleStringProperty due to lack of type info. The type info might be written as a part of an object though (fully qualified name, a special code representing the real type, etc), but I don't think you need it so far.
Next, the ObservableListTypeAdapterFactory implementation:
final class ObservableListTypeAdapterFactory
implements TypeAdapterFactory {
private static final TypeAdapterFactory observableListTypeAdapterFactory = new ObservableListTypeAdapterFactory();
private ObservableListTypeAdapterFactory() {
}
static TypeAdapterFactory getObservableListTypeAdapterFactory() {
return observableListTypeAdapterFactory;
}
#Override
public <T> TypeAdapter<T> create(final Gson gson, final TypeToken<T> typeToken) {
if ( ObservableList.class.isAssignableFrom(typeToken.getRawType()) ) {
final ParameterizedType parameterizedType = (ParameterizedType) typeToken.getType();
final Class<?> elementClass = (Class<?>) parameterizedType.getActualTypeArguments()[0];
final TypeAdapter<?> elementTypeAdapter = gson.getAdapter(elementClass);
#SuppressWarnings("unchecked")
final TypeAdapter<T> objectObservableListTypeAdapter = (TypeAdapter<T>) getObservableListTypeAdapter(elementTypeAdapter);
return objectObservableListTypeAdapter;
}
return null;
}
}
How it works: Gson uses type tokens to specify types precisely, and asks the factory if it can handle the type supplied with the type token. If not, then null is returned. In the implementation above, the very first check is checking if the given type is ObservableList, and then its element type is extracted from the parameterized type info, and then a new observable list type adapter is created and returned.
final class ObservableListTypeAdapter<E>
extends TypeAdapter<ObservableList<E>> {
private final TypeAdapter<E> elementTypeAdapter;
private ObservableListTypeAdapter(final TypeAdapter<E> elementTypeAdapter) {
this.elementTypeAdapter = elementTypeAdapter;
}
static <E> TypeAdapter<ObservableList<E>> getObservableListTypeAdapter(final TypeAdapter<E> elementTypeAdapter) {
return new ObservableListTypeAdapter<>(elementTypeAdapter);
}
#Override
#SuppressWarnings("resource")
public void write(final JsonWriter out, final ObservableList<E> value)
throws IOException {
out.beginArray();
for ( final E element : value ) {
elementTypeAdapter.write(out, element);
}
out.endArray();
}
#Override
public ObservableList<E> read(final JsonReader in)
throws IOException {
final ObservableList<E> list = observableArrayList();
in.beginArray();
while ( in.peek() != END_ARRAY ) {
list.add(elementTypeAdapter.read(in));
}
in.endArray();
return list;
}
}
This one is similar to ObservableStringValueTypeAdapter and deals with ObservableList instances and their respective arrays. Again, TypeAdapter<T> instead of Json(De)Serializer<T> here in order not to create an intermediate JsonArray saving memory and performance. It's a bit more complex because it generates and parses JSON arrays, but only for the [ and ] tokens: elements serialization is performed by the delegated elementTypeAdapter instance.
Now let's put it all together:
public final class Q42210761 {
private Q42210761() {
}
private static final Type fooBarObservableListType = new TypeToken<ObservableList<FooBar>>() {
}.getType();
private static final Gson gson = new GsonBuilder()
.registerTypeHierarchyAdapter(ObservableStringValue.class, getObservableStringValueTypeAdapter())
.registerTypeAdapterFactory(getObservableListTypeAdapterFactory())
.create();
public static void main(final String... args) {
final ObservableList<FooBar> source = observableArrayList(
new FooBar("foo-1", "bar-1"),
new FooBar("foo-2", "bar-2"),
new FooBar("foo-3", "bar-3")
);
out.println(source);
final String json = gson.toJson(source, fooBarObservableListType);
out.println(json);
final ObservableList<?> destination = gson.fromJson(json, fooBarObservableListType);
out.println(destination);
}
}
Note how the Gson instance is configured above. And here is the effective output:
[FooBar{foo=StringProperty [value: foo-1], bar=StringProperty [value: bar-1]}, FooBar{foo=StringProperty [value: foo-2], bar=StringProperty [value: bar-2]}, FooBar{foo=StringProperty [value: foo-3], bar=StringProperty [value: bar-3]}]
[{"foo":"foo-1","bar":"bar-1"},{"foo":"foo-2","bar":"bar-2"},{"foo":"foo-3","bar":"bar-3"}]
[FooBar{foo=StringProperty [value: foo-1], bar=StringProperty [value: bar-1]}, FooBar{foo=StringProperty [value: foo-2], bar=StringProperty [value: bar-2]}, FooBar{foo=StringProperty [value: foo-3], bar=StringProperty [value: bar-3]}]
Note that the JSON output is now much shorter, contains no other elements, and can be safely deserialized back.
I'm trying to implement some simple Json serialization functionality but I'm having a hard time coping with the massive complexity of Gson.
So basically I have a bunch of Entity classes which reference each other with a lot of circular reference. To serialize this structure to JSON I want to keep track of the objects already serialized. The Entity classes all implement an interface called Identified which has one method String getId() giving a globally unique id. So during serializiation of one root element, I want to store all encountered ids in a Set and decide based on that set, whether to fully serialize an object or to serialize that object as a stub
"something": {
"__stub": "true",
"id": "..."
}
This shouldn't be too hard a task in my opinion, but I haven't been able to put something together. Using a custom JsonSerializer I'm not able to have an object (that is not to be serialized as a stub) serialized in the default way. Using a TypeAdapterFactory, I'm not able to access the actual object.
So, any help on how to achieve this, would be very nice!
Best regards
I'm not sure if it's possible easily. As far as I know, Gson promotes immutability and seems to lack custom serialization context support (at least I don't know if it's possible to use custom JsonSerializationContext wherever possible). Thus, one of possible work-around might be the following:
IIdentifiable.java
A simple interface to request a custom ID for an object.
interface IIdentifiable<ID> {
ID getId();
}
Entity.java
A simple entity that can hold another entity references in two manners:
a direct dependency to a "next" entity;
a collection of references to other references.
final class Entity
implements IIdentifiable<String> {
#SerializedName(ID_PROPERTY_NAME)
private final String id;
private final Collection<Entity> entities = new ArrayList<>();
private Entity next;
private Entity(final String id) {
this.id = id;
}
static Entity entity(final String id) {
return new Entity(id);
}
#Override
public String getId() {
return id;
}
Entity setAll(final Entity... entities) {
this.entities.clear();
this.entities.addAll(asList(entities));
return this;
}
Entity setNext(final Entity next) {
this.next = next;
return this;
}
}
IdentitySerializingTypeAdapterFactory.java
I didn't find any easier way rather than making it a type adapter factory, and, unfortunately, this implementation is totally stateful and cannot be reused.
final class IdentitySerializingTypeAdapterFactory
implements TypeAdapterFactory {
private final Collection<Object> traversedEntityIds = new HashSet<>();
private IdentitySerializingTypeAdapterFactory() {
}
static TypeAdapterFactory identitySerializingTypeAdapterFactory() {
return new IdentitySerializingTypeAdapterFactory();
}
#Override
public <T> TypeAdapter<T> create(final Gson gson, final TypeToken<T> typeToken) {
final boolean isIdentifiable = IIdentifiable.class.isAssignableFrom(typeToken.getRawType());
final TypeAdapter<T> delegateAdapter = gson.getDelegateAdapter(this, typeToken);
if ( isIdentifiable ) {
return new TypeAdapter<T>() {
#Override
public void write(final JsonWriter out, final T value)
throws IOException {
final IIdentifiable<?> identifiable = (IIdentifiable<?>) value;
final Object id = identifiable.getId();
if ( !traversedEntityIds.contains(id) ) {
delegateAdapter.write(out, value);
traversedEntityIds.add(id);
} else {
out.beginObject();
out.name(REF_ID_PROPERTY_NAME);
writeSimpleValue(out, id);
out.endObject();
}
}
#Override
public T read(final JsonReader in) {
throw new UnsupportedOperationException();
}
};
}
return delegateAdapter;
}
}
The type adapter firstly tries to check if a given entity has been already traversed. If yes, then it's writing a special object similar to your one (the behavior could be rewritten via the strategy pattern, of course, but let it be more simple). If no, then the default type adapter is obtained, and then the given entity is delegated to that adapter, and registered as a traversed one if the latter type adapter succeeds.
The rest
And here is the rest.
SystemNames.java
final class SystemNames {
private SystemNames() {
}
private static final String SYSTEM_PREFIX = "__$";
static final String ID_PROPERTY_NAME = SYSTEM_PREFIX + "id";
static final String REF_ID_PROPERTY_NAME = SYSTEM_PREFIX + "refId";
}
GsonJsonWriters.java
final class GsonJsonWriters {
private GsonJsonWriters() {
}
static void writeSimpleValue(final JsonWriter writer, final Object value)
throws IOException {
if ( value == null ) {
writer.nullValue();
} else if ( value instanceof Double ) {
writer.value((double) value);
} else if ( value instanceof Long ) {
writer.value((long) value);
} else if ( value instanceof String ) {
writer.value((String) value);
} else if ( value instanceof Boolean ) {
writer.value((Boolean) value);
} else if ( value instanceof Number ) {
writer.value((Number) value);
} else {
throw new IllegalArgumentException("Cannot handle values of type " + value);
}
}
}
Testing
In the test below, there are three entities identified by FOO, BAR, and BAZ string identifiers. All of them have circular dependencies like this:
FOO -> BAR, BAR -> BAZ, BAZ -> FOO using the next property;
FOO -> [BAR, BAZ], BAR -> [FOO, BAZ], BAZ -> [FOO, BAR] using the entities property.
Since the type adapter factory is stateful, even GsonBuilder must be created from scratch thus not having "spoiled" state between use. Simply speaking, once a Gson instance is used once, it must be disposed, so there are GsonBuilder suppliers in the test below.
public final class Q41213747Test {
private static final Entity foo = entity("FOO");
private static final Entity bar = entity("BAR");
private static final Entity baz = entity("BAZ");
static {
foo.setAll(bar, baz).setNext(bar);
bar.setAll(foo, baz).setNext(baz);
baz.setAll(foo, bar).setNext(foo);
}
#Test
public void testSerializeSameJson() {
final String json1 = newSerializingGson().toJson(foo);
final String json2 = newSerializingGson().toJson(foo);
assertThat("Must be the same between the calls because the GSON instances are stateful", json1, is(json2));
}
#Test
public void testSerializeNotSameJson() {
final Gson gson = newSerializingGson();
final String json1 = gson.toJson(foo);
final String json2 = gson.toJson(foo);
assertThat("Must not be the same between the calls because the GSON instance is stateful", json1, is(not(json2)));
}
#Test
public void testOutput() {
out.println(newSerializingGson().toJson(foo));
}
private static Gson newSerializingGson() {
return newSerializingGson(GsonBuilder::new);
}
private static Gson newSerializingGson(final Supplier<GsonBuilder> defaultGsonBuilderSupplier) {
return defaultGsonBuilderSupplier.get()
.registerTypeAdapterFactory(identitySerializingTypeAdapterFactory())
.create();
}
}
{
"__$id": "FOO",
"entities": [
{
"__$id": "BAR",
"entities": [
{
"__$refId": "FOO"
},
{
"__$id": "BAZ",
"entities": [
{
"__$refId": "FOO"
},
{
"__$refId": "BAR"
}
],
"next": {
"__$refId": "FOO"
}
}
],
"next": {
"__$refId": "BAZ"
}
},
{
"__$refId": "BAZ"
}
],
"next": {
"__$refId": "BAR"
}
}
Deserialization of such stuff looks really complicated. At least using GSON facilities.
Do you consider rethinking your JSON model in order to avoid circular dependencies in JSON output? Maybe decomposing your objects to a single map like Map<ID, Object> and making references transient or #Expose-annotated could be easier for you to use? It would simplify deserialization as well.
Json string:
[
//Object 1
{
TypeName:"CheckSpecificDday",
SpecificDay:"20160413",
Lunar:1
},
{
TypeName:"CheckSpecificDday",
SpecificDay:"20160414",
Lunar:1
},
//Object 2
{
TypeName:"CheckEveryDayDday",
StartDate:"20160413",
EndDate:"20260417",
Interval:1,
StartOption:"D",
HolidayCondition:1
},
//Object 3
{
TypeName:"CheckEveryDdayOfWeek",
StartDate:"20160413",
EndDate:"",
Interval:1,
SpecificDayOfWeek:"3",
HolidayCondition:1
},
//Object 4
{
TypeName:"CheckEveryMonthSpecificDday",
StartDate:"20160413",
EndDate:"",
Interval:1,
SpecificDD:"13,14",
HolidayCondition:1
},
//Object 5
{
TypeName:"CheckEveryYearWeek",
StartDate:"20160413",
EndDate:"",
Interval:1,
SpecificMMnthWeek:"0433",
HolidayCondition:1
}
]
I have a Json array like the above. What I want is to parse it to different object types with Gson (as I commented to make it clearer), but I dont know how to do that. Please help me. Thank you in advance!
I think there are lots of simmilar questions on SO. One, Two
One way to parse this is to use simple
Object[] result = new Gson().fromJson(json, Object[].class);
But this will give you objects of LinkedTreeMap<Integer, LinkedTreeMap<String, String>> or something like this. You can use it, but its kinda hard and you will also have problems with your integers comming as doubles.
The other approach is to create custom interface or abstract class with TypeName field if you need it:
private interface CheckInterface{}
and implement it with every POJO classes of object types you have:
private static class CheckEveryDayBase implements CheckInterface{
private String StartDate;
private String EndDate;
private int Interval;
private int HolidayCondition;
}
private static class CheckSpecificDday implements CheckInterface{
private String SpecificDay;
private int Lunar;
}
private static class CheckEveryDayDday extends CheckEveryDayBase{
private String StartOption;
}
private static class CheckEveryDdayOfWeek extends CheckEveryDayBase{
private String SpecificDayOfWeek;
}
private static class CheckEveryMonthSpecificDday extends CheckEveryDayBase{
private String SpecificDD;
}
private static class CheckEveryYearWeek extends CheckEveryDayBase{
private String SpecificMMnthWeek;
}
Then create custom desrializer for your CheckInterface:
public static class CheckInterfaceDeserializer implements JsonDeserializer<CheckInterface>{
#Override
public CheckInterface deserialize(JsonElement json, Type typeOfT,
JsonDeserializationContext context) throws JsonParseException {
JsonObject jObject = (JsonObject) json;
JsonElement typeObj = jObject.get("TypeName");
if(typeObj!= null ){
String typeVal = typeObj.getAsString();
switch (typeVal){
case "CheckSpecificDday":
return context.deserialize(json, CheckSpecificDday.class);
case "CheckEveryDayDday":
return context.deserialize(json, CheckEveryDayDday.class);
case "CheckEveryDdayOfWeek":
return context.deserialize(json, CheckEveryDdayOfWeek.class);
case "CheckEveryMonthSpecificDday":
return context.deserialize(json, CheckEveryMonthSpecificDday.class);
case "CheckEveryYearWeek":
return context.deserialize(json, CheckEveryYearWeek.class);
}
}
return null;
}
}
Here is how you can use this:
GsonBuilder builder = new GsonBuilder();
// Register custom deserializer for CheckInterface.class
builder.registerTypeAdapter(CheckInterface.class, new CheckInterfaceDeserializer());
Gson gson = builder.create();
CheckInterface[] result2 = gson.fromJson(json, CheckInterface[].class);