I want to persist some data that is only relevant on the client. I'm like to intentionally ignore database normalization since the data is pretty useless on the server side.
I could accomplish this trivially by having the client convert the data to JSON and include the String in the JSON sent in the request. However, I would prefer a cleaner more elegant solution.
What I'd like to have:
Given
class MyEntity {
String someString;
int someInt;
#Lob String clientData;
}
and an input
{
someString: "The answer",
someInt: 43,
clientData: {
x: [1, 1, 2, 3, 5, 8, 13],
y: [1, 1, 2, 6, 24, 120],
tonsOfComplicatedStuff: {stuff: stuff}
}
}
store the clientData packed as JSON in a single column. Note that I don't want to write an adapter for MyEntity as there are many columns. I need an adapter for the single column. The column type needn't be a String (Serializable or anything else would do, as the server really doesn't care).
Gson supports the #JsonAdapter annotation allowing to specify a JSON (de)serializer, type adapter, or even a type adapter factory. And the annotation looks like a good candidate to annotate the clientData field in MyEntity:
final class MyEntity {
String someString;
int someInt;
#Lob
#JsonAdapter(PackedJsonTypeAdapterFactory.class)
String clientData;
}
The type adapter factory may look as follows:
final class PackedJsonTypeAdapterFactory
implements TypeAdapterFactory {
// Gson can instantiate this itself
private PackedJsonTypeAdapterFactory() {
}
#Override
public <T> TypeAdapter<T> create(final Gson gson, final TypeToken<T> typeToken) {
#SuppressWarnings("unchecked")
final TypeAdapter<T> typeAdapter = (TypeAdapter<T>) new PackedJsonTypeAdapter(gson);
return typeAdapter;
}
private static final class PackedJsonTypeAdapter
extends TypeAdapter<String> {
private final Gson gson;
private PackedJsonTypeAdapter(final Gson gson) {
this.gson = gson;
}
#Override
public void write(final JsonWriter out, final String json) {
final JsonElement jsonElement = gson.fromJson(json, JsonElement.class);
gson.toJson(jsonElement, out);
}
#Override
public String read(final JsonReader in) {
final JsonElement jsonElement = gson.fromJson(in, JsonElement.class);
return jsonElement != null ? jsonElement.toString() : null;
}
}
}
Note that this converter strategy is implemented as a type adapter factory, since this is the only way of accessing the Gson instance known to me, and JsonSerializer/JsonDeserializer do not seem to make good parsing via the serialization context. Another pitfall here is that this implementation is tree-based requiring JSON trees to be stored in memory completely. In theory, there could be a nice stream-oriented implementation like gson.fromJson(jsonReader) -> JsonReader or a JsonReader->Reader decorator to be redirected to a StringWriter for example, but I couldn't find any alternative for really long time.
public static void main(final String... args) {
final Gson gson = new Gson();
out.println("deserialization:");
final String incomingJson = "{someString:\"The answer\",someInt:43,clientData:{x:[1,1,2,3,5,8,13],y:[1,1,2,6,24,120],tonsOfComplicatedStuff:{stuff:stuff}}}";
final MyEntity myEntity = gson.fromJson(incomingJson, MyEntity.class);
out.println("\t" + myEntity.someString);
out.println("\t" + myEntity.someInt);
out.println("\t" + myEntity.clientData);
out.println("serialization:");
final String outgoingJson = gson.toJson(myEntity);
out.println("\t" + outgoingJson);
out.println("equality check:");
out.println("\t" + areEqual(gson, incomingJson, outgoingJson));
}
private static boolean areEqual(final Gson gson, final String incomingJson, final String outgoingJson) {
final JsonElement incoming = gson.fromJson(incomingJson, JsonElement.class);
final JsonElement outgoing = gson.fromJson(outgoingJson, JsonElement.class);
return incoming.equals(outgoing);
}
The output:
deserialization:
The answer
43
{"x":[1,1,2,3,5,8,13],"y":[1,1,2,6,24,120],"tonsOfComplicatedStuff":{"stuff":"stuff"}}
serialization:
{"someString":"The answer","someInt":43,"clientData":{"x":[1,1,2,3,5,8,13],"y":[1,1,2,6,24,120],"tonsOfComplicatedStuff":{"stuff":"stuff"}}}
equality check:
true
Don't know if it can play with Hibernate nicely, though.
Edit
Despite JSON-packed strings are collected into the memory, streaming may be cheaper for various reasons and can save some memory. Another advantage of streaming is that such a JSON-packing type adapter does not need a type adapter factory anymore and Gson instances therefore keeping a JSON stream as-is, however still making some normalizations like {stuff:stuff} -> {"stuff":"stuff"}. For example:
#JsonAdapter(PackedJsonStreamTypeAdapter.class)
String clientData;
final class PackedJsonStreamTypeAdapter
extends TypeAdapter<String> {
private PackedJsonStreamTypeAdapter() {
}
#Override
public void write(final JsonWriter out, final String json)
throws IOException {
#SuppressWarnings("resource")
final Reader reader = new StringReader(json);
writeNormalizedJsonStream(new JsonReader(reader), out);
}
#Override
public String read(final JsonReader in)
throws IOException {
#SuppressWarnings("resource")
final Writer writer = new StringWriter();
writeNormalizedJsonStream(in, new JsonWriter(writer));
return writer.toString();
}
}
final class JsonStreams {
private JsonStreams() {
}
static void writeNormalizedJsonStream(final JsonReader reader, final JsonWriter writer)
throws IOException {
writeNormalizedJsonStream(reader, writer, true);
}
#SuppressWarnings("resource")
static void writeNormalizedJsonStream(final JsonReader reader, final JsonWriter writer, final boolean isLenient)
throws IOException {
int level = 0;
for ( JsonToken token = reader.peek(); token != null; token = reader.peek() ) {
switch ( token ) {
case BEGIN_ARRAY:
reader.beginArray();
writer.beginArray();
++level;
break;
case END_ARRAY:
reader.endArray();
writer.endArray();
if ( --level == 0 && isLenient ) {
return;
}
break;
case BEGIN_OBJECT:
reader.beginObject();
writer.beginObject();
++level;
break;
case END_OBJECT:
reader.endObject();
writer.endObject();
if ( --level == 0 && isLenient ) {
return;
}
break;
case NAME:
final String name = reader.nextName();
writer.name(name);
break;
case STRING:
final String s = reader.nextString();
writer.value(s);
break;
case NUMBER:
final String rawN = reader.nextString();
final Number n;
final Long l = Longs.tryParse(rawN);
if ( l != null ) {
n = l;
} else {
final Double d = Doubles.tryParse(rawN);
if ( d != null ) {
n = d;
} else {
throw new AssertionError(rawN); // must never happen
}
}
writer.value(n);
break;
case BOOLEAN:
final boolean b = reader.nextBoolean();
writer.value(b);
break;
case NULL:
reader.nextNull();
writer.nullValue();
break;
case END_DOCUMENT:
// do nothing
break;
default:
throw new AssertionError(token);
}
}
}
}
This one parses and generates the same input and output respectively. The Longs.tryParse and Doubles.tryParse methods are taken from Google Guava.
Related
Here is the json schema:
As you can see, rated can be both boolean and object.
I am using Retrofit 2 and Gson converter. How should I create my model for this schema?
Here's how I solved this issue:
Create a custom type adapter in your model and parse rated manually;
public class AccountState {
//#SerializedName("rated") //NOPE, parse it manually
private Integer mRated; //also don't name it rated
public Integer getRated() {
return mRated;
}
public void setRated(Integer rated) {
this.mRated = rated;
}
public static class AccountStateDeserializer implements JsonDeserializer<AccountState> {
#Override
public AccountState deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
AccountState accountState = new Gson().fromJson(json, AccountState.class);
JsonObject jsonObject = json.getAsJsonObject();
if (jsonObject.has("rated")) {
JsonElement elem = jsonObject.get("rated");
if (elem != null && !elem.isJsonNull()) {
if(elem.isJsonPrimitive()){
accountState.setRated(null);
}else{
accountState.setRated(elem.getAsJsonObject().get("value").getAsInt());
}
}
}
return accountState ;
}
}
}
Here you create your gson with this custom adapter:
final static Gson gson = new GsonBuilder()
.registerTypeAdapter(AccountState.class, new AccountState.AccountStateDeserializer())
.create();
Add it to retrofit like that:
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(BuildConfig.ENDPOINT)
.addConverterFactory(GsonConverterFactory.create(gson))
.client(okHttpClient)
.build();
TADADADADADADADDAD!
You can make it work without having to implement a custom converter.
All you have to do is put a general "Object" type for the variable and then you just check which data type it is by doing this:
if(object.getClass == YourClass.class){
Whatever we = ((YourClass) object).getWhatever();
} else if(object.getClass == YourOtherClass.class){
String name = ((YourOtherClass) object).getName();
}
You can add as many data types to this variable as you like.
You can also use the java types "String.class", "Boolean.class" or whatever you like.
Gson has a nice feature allowing to inject a custom type adapter or a type adapter factory to a certain field therefore letting Gson to manage the host object and the latter's fields (de)serialization. So, you can be sure that AccountState could be still deserialized with ReflectiveTypeAdapterFactory and ReflectiveTypeAdapterFactory.Adapter so all deserialization strategies defined in GsonBuilder could be applied.
final class AccountState {
// This is what can make life easier. Note its advantages:
// * PackedBooleanTypeAdapterFactory can be reused multiple times
// * AccountState life-cycle can be managed by Gson itself,
// so it can manage *very* complex deserialization automatically.
#JsonAdapter(PackedBooleanTypeAdapterFactory.class)
final Boolean rated = null;
}
Next, how PackageBooleanTypeAdapterFactory is implemented:
final class PackedBooleanTypeAdapterFactory
implements TypeAdapterFactory {
// Gson can instantiate this itself, no need to expose
private PackedBooleanTypeAdapterFactory() {
}
#Override
public <T> TypeAdapter<T> create(final Gson gson, final TypeToken<T> typeToken) {
// Check if it's the type we can handle ourself
if ( typeToken.getRawType() == Boolean.class ) {
final TypeAdapter<Boolean> typeAdapter = new PackedIntegerTypeAdapter(gson);
// Some Java "unchecked" boilerplate here...
#SuppressWarnings("unchecked")
final TypeAdapter<T> castTypeAdapter = (TypeAdapter<T>) typeAdapter;
return castTypeAdapter;
}
// If it's something else, let Gson pick a downstream type adapter on its own
return null;
}
private static final class PackedIntegerTypeAdapter
extends TypeAdapter<Boolean> {
private final Gson gson;
private PackedIntegerTypeAdapter(final Gson gson) {
this.gson = gson;
}
#Override
public void write(final JsonWriter out, final Boolean value) {
throw new UnsupportedOperationException();
}
#Override
public Boolean read(final JsonReader in)
throws MalformedJsonException {
// Pick next token as a JsonElement
final JsonElement jsonElement = gson.fromJson(in, JsonElement.class);
// Note that Gson uses JsonNull singleton to denote a null
if ( jsonElement.isJsonNull() ) {
return null;
}
if ( jsonElement.isJsonPrimitive() ) {
return jsonElement
.getAsJsonPrimitive()
.getAsBoolean();
}
if ( jsonElement.isJsonObject() ) {
return jsonElement
.getAsJsonObject()
.getAsJsonPrimitive("value")
.getAsBoolean();
}
// Not something we can handle
throw new MalformedJsonException("Cannot parse: " + jsonElement);
}
}
}
Demo:
public static void main(final String... args) {
parseAndDump("{\"rated\":null}");
parseAndDump("{\"rated\":true}");
parseAndDump("{\"rated\":{\"value\":true}}");
}
private static void parseAndDump(final String json) {
final AccountState accountState = gson.fromJson(json, AccountState.class);
System.out.println(accountState.rated);
}
Output:
null
true
true
Note that JsonSerializer and JsonDeserializer both have some performance and memory cost due to its tree model design (you can traverse JSON trees easily as long as they are in memory). Sometimes, for simple cases, a streaming type adapter may be preferable. Pros: consumes less memory and works faster. Cons: hard to implement.
final class AccountState {
#JsonAdapter(PackedBooleanTypeAdapter.class)
final Boolean rated = null;
}
Note that the rated field accepts a type adapter directly because it does not need Gson instances to build JSON trees (JsonElements).
final class PackedBooleanTypeAdapter
extends TypeAdapter<Boolean> {
// Gson still can instantiate this type adapter itself
private PackedBooleanTypeAdapter() {
}
#Override
public void write(final JsonWriter out, final Boolean value) {
throw new UnsupportedOperationException();
}
#Override
public Boolean read(final JsonReader in)
throws IOException {
// Peeking the next JSON token and dispatching parsing according to the given token
final JsonToken token = in.peek();
switch ( token ) {
case NULL:
return parseAsNull(in);
case BOOLEAN:
return parseAsBoolean(in);
case BEGIN_OBJECT:
return parseAsObject(in);
// The below might be omitted, since some code styles prefer all switch/enum constants explicitly
case BEGIN_ARRAY:
case END_ARRAY:
case END_OBJECT:
case NAME:
case STRING:
case NUMBER:
case END_DOCUMENT:
throw new MalformedJsonException("Cannot parse: " + token);
// Not a known token, and must never happen -- something new in a newer Gson version?
default:
throw new AssertionError(token);
}
}
private Boolean parseAsNull(final JsonReader in)
throws IOException {
// null token still has to be consumed from the reader
in.nextNull();
return null;
}
private Boolean parseAsBoolean(final JsonReader in)
throws IOException {
// Consume a boolean value from the reader
return in.nextBoolean();
}
private Boolean parseAsObject(final JsonReader in)
throws IOException {
// Consume the begin object token `{`
in.beginObject();
// Get the next property name
final String property = in.nextName();
// Not a value? Then probably it's not what we're expecting for
if ( !property.equals("value") ) {
throw new MalformedJsonException("Unexpected property: " + property);
}
// Assuming the property "value" value must be a boolean
final boolean value = in.nextBoolean();
// Consume the object end token `}`
in.endObject();
return value;
}
}
This one should work faster. The output remains the same. Note that Gson does not require a GsonBuilder for both cases. As far as I remember how Retrofit 2 works, GsonConverterFactory is still required (not sure, Gson is not the default serializer in Retrofit 2?).
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 have bunch of model classes which have fields of type List<X> where X is one of many things (e.g. String, Integer, but also some of my own types). I'm using GSON to parse JSON representations of these models.
My problem is that the server I'm dealing with (which is beyond my control) somehow removed singleton arrays and replaces them by the contained object.
For example, instead of returning:
{
"foo": [ "bar"],
"bleh": [ { "some": "object" } ]
}
It returns:
{
"foo": "bar",
"bleh": { "some": "object" }
}
Now assume that the Java model class look something like this:
public class Model {
private List<String> foo;
private List<SomeObject> bleh;
}
Currently this causes GSON to throw an exception because it finds BEGIN_STRING or BEGIN_OBJECT where it expects BEGIN_ARRAY.
For arrays or lists of Strings this is easily solved using a TypeAdapter<List<String>>. But the problem is I have Lists with many different element types and I don't want to write a separate TypeAdapter for each case. Nor have I been able to a generic TypeAdapter<List<?>>, because at some point you need to know the type.
So is there another way to configure GSON to be smart enough to turn single objects or values into arrays/lists? Or in other words, just "pretend" that the [ and ] are there where it expects to find them although they aren't there?
But the problem is I have Lists with many different element types and I don't want to write a separate TypeAdapter for each case. Nor have I been able to a generic TypeAdapter>, because at some point you need to know the type.
This is what type adapter factories are designed for: you can control every type in Gson instance configuration.
final class AlwaysListTypeAdapterFactory<E>
implements TypeAdapterFactory {
// Gson can instantiate it itself
private AlwaysListTypeAdapterFactory() {
}
#Override
public <T> TypeAdapter<T> create(final Gson gson, final TypeToken<T> typeToken) {
// If it's not a List -- just delegate the job to Gson and let it pick the best type adapter itself
if ( !List.class.isAssignableFrom(typeToken.getRawType()) ) {
return null;
}
// Resolving the list parameter type
final Type elementType = resolveTypeArgument(typeToken.getType());
#SuppressWarnings("unchecked")
final TypeAdapter<E> elementTypeAdapter = (TypeAdapter<E>) gson.getAdapter(TypeToken.get(elementType));
// Note that the always-list type adapter is made null-safe, so we don't have to check nulls ourselves
#SuppressWarnings("unchecked")
final TypeAdapter<T> alwaysListTypeAdapter = (TypeAdapter<T>) new AlwaysListTypeAdapter<>(elementTypeAdapter).nullSafe();
return alwaysListTypeAdapter;
}
private static Type resolveTypeArgument(final Type type) {
// The given type is not parameterized?
if ( !(type instanceof ParameterizedType) ) {
// No, raw
return Object.class;
}
final ParameterizedType parameterizedType = (ParameterizedType) type;
return parameterizedType.getActualTypeArguments()[0];
}
private static final class AlwaysListTypeAdapter<E>
extends TypeAdapter<List<E>> {
private final TypeAdapter<E> elementTypeAdapter;
private AlwaysListTypeAdapter(final TypeAdapter<E> elementTypeAdapter) {
this.elementTypeAdapter = elementTypeAdapter;
}
#Override
public void write(final JsonWriter out, final List<E> list) {
throw new UnsupportedOperationException();
}
#Override
public List<E> read(final JsonReader in)
throws IOException {
// This is where we detect the list "type"
final List<E> list = new ArrayList<>();
final JsonToken token = in.peek();
switch ( token ) {
case BEGIN_ARRAY:
// If it's a regular list, just consume [, <all elements>, and ]
in.beginArray();
while ( in.hasNext() ) {
list.add(elementTypeAdapter.read(in));
}
in.endArray();
break;
case BEGIN_OBJECT:
case STRING:
case NUMBER:
case BOOLEAN:
// An object or a primitive? Just add the current value to the result list
list.add(elementTypeAdapter.read(in));
break;
case NULL:
throw new AssertionError("Must never happen: check if the type adapter configured with .nullSafe()");
case NAME:
case END_ARRAY:
case END_OBJECT:
case END_DOCUMENT:
throw new MalformedJsonException("Unexpected token: " + token);
default:
throw new AssertionError("Must never happen: " + token);
}
return list;
}
}
}
Now you just have to tell Gson which fields are not well-formed.
Of course, you might configure the whole Gson instance to accept such lists, but let it be more precise using the #JsonAdapter annotation:
final class Model {
#JsonAdapter(AlwaysListTypeAdapterFactory.class)
final List<String> foo = null;
#JsonAdapter(AlwaysListTypeAdapterFactory.class)
final List<SomeObject> bleh = null;
#Override
public String toString() {
return "Model{" + "foo=" + foo + ", bleh=" + bleh + '}';
}
}
final class SomeObject {
final String some = null;
#Override
public String toString() {
return "SomeObject{" + "some='" + some + '\'' + '}';
}
}
Test data:
single.json
{
"foo": "bar",
"bleh": {"some": "object"}
}
list.json
{
"foo": ["bar"],
"bleh": [{"some": "object"}]
}
Example:
private static final Gson gson = new Gson();
public static void main(final String... args)
throws IOException {
for ( final String resource : ImmutableList.of("single.json", "list.json") ) {
try ( final JsonReader jsonReader = getPackageResourceJsonReader(Q43412261.class, resource) ) {
final Model model = gson.fromJson(jsonReader, Model.class);
System.out.println(model);
}
}
}
And the output:
Model{foo=[bar], bleh=[SomeObject{some='object'}]}
Model{foo=[bar], bleh=[SomeObject{some='object'}]}
You can simply write your own JsonDeserializer where you check whether your bleh or foo are JsonObjects or JsonArrays.
To check if a JsonElement is an array or an object:
JsonElement element = ...;
if (element.isJsonObject()) {
//element is a JsonObject
} else if (element.isJsonArray()) {
//element is a JsonArray
}
One solution to this would be to write a custom TypeAdapterFactory which creates an adapter which peeks at the JSON data. If it encounters something other than a JSON array (or JSON null) it wraps it inside a JSON array before deserializing it:
// Only intended for usage with #JsonAdapter on fields
class SingleValueOrListAdapterFactory implements TypeAdapterFactory {
#Override
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
// Note: Cannot use getDelegateAdapter due to https://github.com/google/gson/issues/1028
TypeAdapter<T> listAdapterDelegate = gson.getAdapter(type);
TypeAdapter<JsonElement> jsonElementAdapter = gson.getAdapter(JsonElement.class);
return new TypeAdapter<T>() {
#Override
public void write(JsonWriter out, T value) throws IOException {
listAdapterDelegate.write(out, value);
}
#Override
public T read(JsonReader in) throws IOException {
JsonToken peeked = in.peek();
if (peeked == JsonToken.NULL || peeked == JsonToken.BEGIN_ARRAY) {
return listAdapterDelegate.read(in);
} else {
// Wrap JSON element in a new JSON array before deserializing it
JsonElement jsonElement = jsonElementAdapter.read(in);
JsonArray jsonArray = new JsonArray();
jsonArray.add(jsonElement);
return listAdapterDelegate.fromJsonTree(jsonArray);
}
}
};
}
}
The above implementation is designed only for usage with #JsonAdapter on fields, for example:
#JsonAdapter(SingleValueOrListAdapterFactory.class)
private List<MyClass> myField;
Compared to the currently accepted answer this provides the following advantages because it simply delegates the actual deserialization to listAdapterDelegate:
Custom List (or Collection) subclasses are supported because creation of them is delegated to Gson
Gson's default type resolution logic is used to determine the element type and to deserialize it
But it also has the following disadvantage:
Decreased performance because if the data is not already in a JSON array it is first deserialized to a JsonElement before the actual deserialization is performed
When using the GSON library, you could just check whether or not the following token is an object or an array. This of course requires you to go more fine grained while parsing the XML, but it allows you full control of what do you want to get from it. Sometimes we are not under control of the XML, and it could come handy.
This is an example to check if the next token is an object or an array, using the JsonReader class to parse the file:
if (jsonReader.peek() == JsonToken.BEGIN_ARRAY) {
jsonReader.beginArray()
} else if (jsonReader.peek() == JsonToken.BEGIN_OBJECT) {
jsonReader.beginObject()
}
And at the end of the array / object, you could do the same, but for the end tokens:
if (jsonReader.peek() == JsonToken.END_ARRAY) {
jsonReader.endArray()
} else if (jsonReader.peek() == JsonToken.END_OBJECT) {
jsonReader.endObject()
}
This way, you could have identical code (adding an extra check, to verify if you are on an array or on an object) to parse your array of objects, or a single object.
I had this same problem consuming xml / json from a vendor - they certainly weren't going to change their code for me :) There were several resources on the web that I used before changing adapting them to my own version This SO answer was very helpful. I spent some time looking at the gson code and finding a lot of private variable that I wanted access to. So, essentially what my custom collection adapter does is peek to see if the next element is an object. If not, we just delegate the read to the previous adapter (that we have overridden).
If the next element is an object, we use gson to process that. We then convert that to an array of one object. Use gson to write that to a string, then pass that string as a JsonReader to the underlying adapter. This can then create an instance of the underlying list and add the one element we have.
Here's the AdapterTypeFactory:
public enum ListSingleObjectAdapterFactory implements TypeAdapterFactory {
INSTANCE; // Josh Bloch's Enum singleton pattern
#SuppressWarnings({ "unchecked", "rawtypes" })
#Override
public <T> TypeAdapter<T> create(final Gson gson, final TypeToken<T> typeToken) {
Class<? super T> rawType = typeToken.getRawType();
if (!Collection.class.isAssignableFrom(rawType)) {
return null;
}
TypeAdapter collectionAdapter = gson.getDelegateAdapter(this, typeToken);
Class genericClass = (Class) ((ParameterizedType) typeToken.getType())
.getActualTypeArguments()[0];
return new SingleObjectOrCollectionAdapter(
gson, collectionAdapter, genericClass);
}
}
Then the type adapter I have is:
public class SingleObjectOrCollectionAdapter<T> extends TypeAdapter<Collection<T>> {
private Class<T> adapterclass;
private Gson gson;
private TypeAdapter arrayTypeAdapter;
public SingleObjectOrCollectionAdapter(Gson gson, TypeAdapter<T> collectionTypeAdapter, Class<T> componentType) {
arrayTypeAdapter = collectionTypeAdapter;
this.gson = gson;
adapterclass = componentType;
}
#Override
public Collection<T> read(JsonReader reader) throws IOException {
Collection<T> collection;
JsonReader myReader = reader;
if (reader.peek() == JsonToken.BEGIN_OBJECT) {
T inning = gson.fromJson(reader, adapterclass);
String s = gson.toJson(new Object[]{inning});
myReader = new JsonReader(new StringReader(s));
}
collection = (Collection)arrayTypeAdapter.read( myReader );
return collection;
}
#Override
public void write(JsonWriter writer, Collection<T> value) throws IOException {
arrayTypeAdapter.write(writer, value);
}
}
Finally, we need to register the adapter factory:
GsonBuilder gb = new GsonBuilder().registerTypeAdapterFactory(ListSingleObjectAdapterFactory.INSTANCE);
So far, it seems to be working well handling both single and multiple objects - although I wouldn't be surprised if it needs some tweaking down the road.
I have an API that can return JSON arrays or objects. Example JSON object
{
"id": 1,
"name": "name"
}
JSON array:
[
{
"id": 1,
"name": "name"
},
{
"id": 1,
"name": "name"
}
]
When mapping a JSON object response to a POJO I use:
MyEntity myEntity = new Gson().fromJson(jsonString, MyEntity.class);
When mapping a JSON array response to an array of POJOs I use:
MyEntity[] myEntity = new GSON().fromJson(jsonString, MyEntity[].class);
How can I convert those two responses to the appropriate types dynamically?
NOTE: I can't modify the server response, this is a public API.
Thank you!
EDIT:
I am trying to implement a method that does this automatically but I am missing something. The method
public <T> T convertResponseToEntity(Class<T> classOfT)
{
JsonElement jsonElement = this.gson.fromJson(getResponseAsString(), JsonElement.class);
if (jsonElement.isJsonArray()) {
Type listType = new TypeToken<T>(){}.getType();
return this.gson.fromJson(getResponseAsString(), listType);
}
return this.gson.fromJson(getResponseAsString(), (Type) classOfT);
}
It returns a list of LinkedTreeMaps. How can I modify the code to return the same content as Object[]?
How can I convert those 2 responses dynamically to the appropriate type?
It depends on how to interpret the "appropriate type" here because it would lead to instanceof or visitor pattern to get the appropriate type once you try to handle the parsed-from-JSON object every time you need it. If you can't change the API, you can smooth the way you use it. One of possible options here is handling such response as if everything is a list. Even a single object can be handled as a list with one element only (and many libraries work with sequences/lists only having that fact: Stream API in Java, LINQ in .NET, jQuery in JavaScript, etc).
Suppose you have the following MyEntity class to handle the elements obtained from the API you need:
// For the testing purposes, package-visible final fields are perfect
// Gson can deal with final fields too
final class MyEntity {
final int id = Integer.valueOf(0); // not letting javac to inline 0 since it's primitive
final String name = null;
#Override
public String toString() {
return id + "=>" + name;
}
}
Next, let's create a type adapter that will always align "true" lists and single objects as if it were a list:
final class AlwaysListTypeAdapter<T>
extends TypeAdapter<List<T>> {
private final TypeAdapter<T> elementTypeAdapter;
private AlwaysListTypeAdapter(final TypeAdapter<T> elementTypeAdapter) {
this.elementTypeAdapter = elementTypeAdapter;
}
static <T> TypeAdapter<List<T>> getAlwaysListTypeAdapter(final TypeAdapter<T> elementTypeAdapter) {
return new AlwaysListTypeAdapter<>(elementTypeAdapter);
}
#Override
#SuppressWarnings("resource")
public void write(final JsonWriter out, final List<T> list)
throws IOException {
if ( list == null ) {
out.nullValue();
} else {
switch ( list.size() ) {
case 0:
out.beginArray();
out.endArray();
break;
case 1:
elementTypeAdapter.write(out, list.iterator().next());
break;
default:
out.beginArray();
for ( final T element : list ) {
elementTypeAdapter.write(out, element);
}
out.endArray();
break;
}
}
}
#Override
public List<T> read(final JsonReader in)
throws IOException {
final JsonToken token = in.peek();
switch ( token ) {
case BEGIN_ARRAY:
final List<T> list = new ArrayList<>();
in.beginArray();
while ( in.peek() != END_ARRAY ) {
list.add(elementTypeAdapter.read(in));
}
in.endArray();
return unmodifiableList(list);
case BEGIN_OBJECT:
return singletonList(elementTypeAdapter.read(in));
case NULL:
return null;
case END_ARRAY:
case END_OBJECT:
case NAME:
case STRING:
case NUMBER:
case BOOLEAN:
case END_DOCUMENT:
throw new MalformedJsonException("Unexpected token: " + token);
default:
// A guard case: what if Gson would add another token someday?
throw new AssertionError("Must never happen: " + token);
}
}
}
Gson TypeAdapter are designed to work in streaming fashion thus they are cheap from the efficiency perspective, but not that easy in implementation. The write() method above is implemented just for the sake of not putting throw new UnsupportedOperationException(); there (I'm assuming you only read that API, but don't know if that API might consume "either element or a list" modification requests). Now it's necessary to create a type adapter factory to let Gson pick up the right type adapter for every particular type:
final class AlwaysListTypeAdapterFactory
implements TypeAdapterFactory {
private static final TypeAdapterFactory alwaysListTypeAdapterFactory = new AlwaysListTypeAdapterFactory();
private AlwaysListTypeAdapterFactory() {
}
static TypeAdapterFactory getAlwaysListTypeAdapterFactory() {
return alwaysListTypeAdapterFactory;
}
#Override
public <T> TypeAdapter<T> create(final Gson gson, final TypeToken<T> typeToken)
throws IllegalArgumentException {
if ( List.class.isAssignableFrom(typeToken.getRawType()) ) {
final Type elementType = getElementType(typeToken);
// Class<T> instances can be compared with ==
final TypeAdapter<?> elementTypeAdapter = elementType == MyEntity.class ? gson.getAdapter(MyEntity.class) : null;
// Found supported element type adapter?
if ( elementTypeAdapter != null ) {
#SuppressWarnings("unchecked")
final TypeAdapter<T> castTypeAdapter = (TypeAdapter<T>) getAlwaysListTypeAdapter(elementTypeAdapter);
return castTypeAdapter;
}
}
// Not a type that can be handled? Let Gson pick a more appropriate one itself
return null;
}
// Attempt to detect the list element type
private static Type getElementType(final TypeToken<?> typeToken) {
final Type listType = typeToken.getType();
return listType instanceof ParameterizedType
? ((ParameterizedType) listType).getActualTypeArguments()[0]
: Object.class;
}
}
And how it's used after all:
private static final Type responseItemListType = new TypeToken<List<MyEntity>>() {
}.getType();
private static final Gson gson = new GsonBuilder()
.registerTypeAdapterFactory(getAlwaysListTypeAdapterFactory())
.create();
public static void main(final String... args) {
test("");
test("{\"id\":1,\"name\":\"name\"}");
test("[{\"id\":1,\"name\":\"name\"},{\"id\":1,\"name\":\"name\"}]");
test("[]");
}
private static void test(final String incomingJson) {
final List<MyEntity> list = gson.fromJson(incomingJson, responseItemListType);
System.out.print("LIST=");
System.out.println(list);
System.out.print("JSON=");
gson.toJson(list, responseItemListType, System.out); // no need to create an intermediate string, let it just stream
System.out.println();
System.out.println("-----------------------------------");
}
The output:
LIST=null
JSON=null
-----------------------------------
LIST=[1=>name]
JSON={"id":1,"name":"name"}
-----------------------------------
LIST=[1=>name, 1=>name]
JSON=[{"id":1,"name":"name"},{"id":1,"name":"name"}]
-----------------------------------
LIST=[]
JSON=[]
-----------------------------------
Just parse it into JsonElement and check actual element type:
Gson g = new Gson();
JsonParser parser = new JsonParser();
JsonElement e = parser.parse( new StringReader(jsonString) );
if(e instanceof JsonObject) {
MyEntity myEntity = g.fromJson(e, MyEntity.class);
} else {
MyEntity[] myEntity = g.fromJson(e, MyEntity[].class);
}
I'm trying to create an abstract class for defining configuration classes. I wish to export and import these classes from and to JSON whenever I want to. I'm trying to achieve this using Gson.
I'm getting an error when writing to JSON that states it:
can't serialize java.lang.Class - Forgot to register a type adapter?
My main class: https://hastebin.com/pogohodovi.scala
Abstract config class: https://hastebin.com/adeyawubuy.cs
An example of a child class:
public class DyescapeCOREConfiguration extends DyescapeConfiguration {
private static transient DyescapeCOREConfiguration i = new DyescapeCOREConfiguration();
public static DyescapeCOREConfiguration get() { return i; }
#Expose public static String ServerID = UUID.randomUUID().toString();
}
Please note: I need to keep the variables in the child configuration classes static. I tried to create some adapters/serializers, but they don't seem to work.
You're probably doing:
gson.toJson(DyescapeCOREConfiguration.class)
In order to serialize this class, you still must create an instance of DyescapeCOREConfiguration. Since statics are not (de)serialized by default, you have to enable them (IMHO, enabling such modifier is really not a good idea):
final Gson gson = new GsonBuilder()
.excludeFieldsWithoutExposeAnnotation()
.excludeFieldsWithModifiers(TRANSIENT) // STATIC|TRANSIENT in the default configuration
.create();
final String json = gson.toJson(new DyescapeCOREConfiguration());
System.out.println(json);
The output:
{"ServerID":"37145480-64b9-4beb-b031-2d619f14a44b"}
Update
If obtaining an instance is not possible for whatever reason, write a custom Class<?> type adapter (I would never use it in practice):
StaticTypeAdapterFactory.java
final class StaticTypeAdapterFactory
implements TypeAdapterFactory {
private static final TypeAdapterFactory staticTypeAdapterFactory = new StaticTypeAdapterFactory();
private StaticTypeAdapterFactory() {
}
static TypeAdapterFactory getStaticTypeAdapterFactory() {
return staticTypeAdapterFactory;
}
#Override
public <T> TypeAdapter<T> create(final Gson gson, final TypeToken<T> typeToken) {
final Type type = typeToken.getType();
if ( type.equals(Class.class) ) {
#SuppressWarnings("unchecked")
final TypeAdapter<T> castStaticTypeAdapter = (TypeAdapter<T>) getStaticTypeAdapter(gson);
return castStaticTypeAdapter;
}
return null;
}
}
StaticTypeAdapter.java
final class StaticTypeAdapter<T>
extends TypeAdapter<Class<T>> {
private static final String TARGET_CLASS_PROPERTY = "___class";
private final Gson gson;
private StaticTypeAdapter(final Gson gson) {
this.gson = gson;
}
static <T> TypeAdapter<Class<T>> getStaticTypeAdapter(final Gson gson) {
return new StaticTypeAdapter<>(gson);
}
#Override
#SuppressWarnings("resource")
public void write(final JsonWriter out, final Class<T> value)
throws IOException {
try {
final Iterator<Field> iterator = Stream.of(value.getFields())
.filter(f -> isStatic(f.getModifiers()))
.iterator();
out.beginObject();
while ( iterator.hasNext() ) {
final Field field = iterator.next();
out.name(field.getName());
field.setAccessible(true);
final Object fieldValue = field.get(null);
#SuppressWarnings({ "unchecked", "rawtypes" })
final TypeAdapter<Object> adapter = (TypeAdapter) gson.getAdapter(field.getType());
adapter.write(out, fieldValue);
}
out.name(TARGET_CLASS_PROPERTY);
out.value(value.getName());
out.endObject();
} catch ( final IllegalAccessException ex ) {
throw new IOException(ex);
}
}
#Override
public Class<T> read(final JsonReader in)
throws IOException {
try {
Class<?> type = null;
in.beginObject();
final Map<String, JsonElement> buffer = new HashMap<>();
while ( in.peek() != END_OBJECT ) {
final String property = in.nextName();
switch ( property ) {
case TARGET_CLASS_PROPERTY:
type = Class.forName(in.nextString());
break;
default:
// buffer until the target class name is known
if ( type == null ) {
final TypeAdapter<JsonElement> adapter = gson.getAdapter(JsonElement.class);
final JsonElement jsonElement = adapter.read(in);
buffer.put(property, jsonElement);
} else {
// flush the buffer
if ( !buffer.isEmpty() ) {
for ( final Entry<String, JsonElement> e : buffer.entrySet() ) {
final Field field = type.getField(e.getKey());
final Object value = gson.getAdapter(field.getType()).read(in);
field.set(null, value);
}
buffer.clear();
}
final Field field = type.getField(property);
if ( isStatic(field.getModifiers()) ) {
final TypeAdapter<?> adapter = gson.getAdapter(field.getType());
final Object value = adapter.read(in);
field.set(null, value);
}
}
break;
}
}
in.endObject();
// flush the buffer
if ( type != null && !buffer.isEmpty() ) {
for ( final Entry<String, JsonElement> e : buffer.entrySet() ) {
final Field field = type.getField(e.getKey());
final Object value = gson.fromJson(e.getValue(), field.getType());
field.set(null, value);
}
buffer.clear();
}
#SuppressWarnings({ "unchecked", "rawtypes" })
final Class<T> castType = (Class) type;
return castType;
} catch ( final ClassNotFoundException | NoSuchFieldException | IllegalAccessException ex ) {
throw new IOException(ex);
}
}
}
Example use:
final Gson gson = new GsonBuilder()
.registerTypeAdapterFactory(getStaticTypeAdapterFactory())
.create();
final String json = gson.toJson(DyescapeCOREConfiguration.class);
out.println("DyescapeCOREConfiguration.ServerID=" + DyescapeCOREConfiguration.ServerID);
// ---
DyescapeCOREConfiguration.ServerID = "whatever";
out.println("DyescapeCOREConfiguration.ServerID=" + DyescapeCOREConfiguration.ServerID);
// ---
#SuppressWarnings("unchecked")
final Class<DyescapeCOREConfiguration> configurationClass = gson.fromJson(json, Class.class);
// ^--- this is awful, omitting a useless assignment is even worse
out.println("DyescapeCOREConfiguration.ServerID=" + DyescapeCOREConfiguration.ServerID);
Output:
DyescapeCOREConfiguration.ServerID=012fa795-abd8-4b91-b6f5-bab67f73ae17
DyescapeCOREConfiguration.ServerID=whatever
DyescapeCOREConfiguration.ServerID=012fa795-abd8-4b91-b6f5-bab67f73ae17
However, I still recommend you to avoid the idea of static fields (de)serialization.