I've been trying to follow the advice given here to turn off scientific notation on numeric values represented in Json. The problem I've got is that my custom Serializer is never called.
I've tried different variations of the code and have eventually ended up with:
public class TestExternaliser {
static class SpecialSerializer implements JsonSerializer<Object> {
#Override
public JsonElement serialize(Object x,
Type type,
JsonSerializationContext jsonSerializationContext) {
return new JsonPrimitive("xxx");
}
}
public static void main(String... args) {
JsonObject root = new JsonObject();
root.addProperty("String", "String");
root.addProperty("Num", Integer.valueOf(123));
root.addProperty("Bool", Boolean.TRUE);
Gson gson = new GsonBuilder()
.registerTypeHierarchyAdapter(Object.class, new SpecialSerializer())
.setPrettyPrinting()
.create();
System.out.println(gson.toJson(root));
}
}
If I've understood the API correctly then this code use the custom serialisation for all values so it should generate "xxx" for all values, but what I keep getting is:
{
"String": "String",
"Num": 123,
"Bool": true
}
What's going wrong?
What's going wrong?
Nothing wrong because of the limitations Gson has by design: Object and JsonElement type adapter hierarchies cannot be overridden.
Here is the test covering all four object/number hierarchy and value/JSON tree pairs:
public final class LimitationsTest {
private static final JsonSerializer<Object> defaultJsonSerializer = (src, typeOfSrc, context) -> new JsonPrimitive("xxx");
private static final Gson objectDefaultsGson = new GsonBuilder()
.registerTypeHierarchyAdapter(Object.class, defaultJsonSerializer)
.create();
private static final Gson numberDefaultsGson = new GsonBuilder()
.registerTypeHierarchyAdapter(Number.class, defaultJsonSerializer)
.create();
private static final class Value {
#SerializedName("String")
private String string;
#SerializedName("Num")
private Number num;
#SerializedName("Bool")
private Boolean bool;
}
private static final Object object;
private static final JsonElement jsonElement;
static {
final Value newObject = new Value();
newObject.string = "String";
newObject.num = 123;
newObject.bool = Boolean.TRUE;
object = newObject;
final JsonObject newJsonElement = new JsonObject();
newJsonElement.addProperty("String", "String");
newJsonElement.addProperty("Num", 123);
newJsonElement.addProperty("Bool", Boolean.TRUE);
jsonElement = newJsonElement;
}
#Test
public void testObjectObject() {
Assertions.assertEquals("\"xxx\"", objectDefaultsGson.toJson(object));
}
#Test
public void testObjectJsonElement() {
Assertions.assertEquals("{\"String\":\"String\",\"Num\":123,\"Bool\":true}", objectDefaultsGson.toJson(jsonElement));
}
#Test
public void testNumberObject() {
Assertions.assertEquals("{\"String\":\"String\",\"Num\":\"xxx\",\"Bool\":true}", numberDefaultsGson.toJson(object));
}
#Test
public void testNumberJsonElement() {
Assertions.assertEquals("{\"String\":\"String\",\"Num\":123,\"Bool\":true}", numberDefaultsGson.toJson(jsonElement));
}
}
In short JsonElements are considered already-serialized, so what you're looking for is hidden in testNumberObject: define Number as a superclass (or Float/Double to be most precise), and serialize an object containing fields, not JsonElement. If you must use JsonElement, then put a "good-formattible" value right into the Num property (BigDecimal should work just fine).
Update 1.
#Test
public void testNoScientificNotationForJsonElement() {
final JsonObject newJsonElement = new JsonObject();
newJsonElement.addProperty("a", new BigDecimal(new BigDecimal("1E+10").toPlainString()));
newJsonElement.addProperty("b", new BigDecimal("1E+10") {
#Override
public String toString() {
return toPlainString();
}
});
final Gson gson = new Gson();
Assertions.assertEquals("{\"a\":10000000000,\"b\":10000000000}", gson.toJson(newJsonElement));
}
In playwright, a microsoft library, and rust-rcon library, something similar happened to me. I leave you link.
This error occurs because you have installed jdk 11 or upper and a gson prior to 2.8.6
https://github.com/microsoft/playwright-java/issues/245#issuecomment-775351308
https://github.com/MrGraversen/rust-rcon/pull/2#event-4300625968
The solution was to go to the latest version of gson, although the version was the one they used, I added it to my POM to force maven to make the rest of the dependencies use the latest version. Try to see and tell me!
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.6</version>
</dependency>
Try this solution :D
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?).
Alternatively, the question could be titled: Avoiding using a Singleton Util as a Gson wrapper.
In my Android project that uses Room for persistence, I have quite a few entities where certain columns are non-primitive types. After looking around for a bit, I found that using TypeConverter along with Gson to convert the non-primitive types to and from strings is a decent solution.
Now, the class which houses the TypeConverters looks like this:
class Converters {
...
#TypeConverter
public static Long fromDate(Date date) {
return date == null ? null : date.getTime();
}
#TypeConverter
public static Date toDate(Long timestamp) {
return timestamp == null ? null : new Date(timestamp);
}
#TypeConverter
public static String fromAddressData(AddressData addressData) {
Gson gson = new GsonBuilder()
.registerTypeAdapter(AddressData.class, new AddressDataAdapter())
.create();
return gson.toJson(addressData);
}
#TypeConverter
public static AddressData toAddressData(String addressDataString) {
Gson gson = new GsonBuilder()
.registerTypeAdapter(AddressData.class, new AddressDataAdapter())
.create();
return gson.fromJson(addressDataString, AddressData.class);
}
#TypeConverter
public static String fromUserData(UserData userData) {
Gson gson = new Gson();
return gson.toJson(userData);
}
#TypeConverter
public static UserData toUserData(String userDataString) {
Gson gson = new Gson();
return gson.fromJson(userDataString, UserData.class);
}
#TypeConverter
public static String fromCollectionData(CollectionData data) {
Gson gson = new GsonBuilder()
.registerTypeAdapter(CollectionData.class, new CollectionDataAdapter())
.create();
return gson.toJson(data, CollectionData.class);
}
#TypeConverter
public static CollectionData toCollectionData(String dataString) {
Gson gson = new GsonBuilder()
.registerTypeAdapter(CollectionData.class, new CollectionDataAdapter())
.create();
return gson.fromJson(dataString, CollectionData.class);
}
#TypeConverter
public static String fromRequestNoteList(List<RequestNote> noteList) {
Gson gson = new Gson();
return gson.toJson(noteList, RequestNote.LIST_TYPE);
}
#TypeConverter
public static List<RequestNote> toRequestNoteList(String noteListString) {
Gson gson = new Gson();
return gson.fromJson(noteListString, RequestNote.LIST_TYPE);
}
#TypeConverter
public static String fromRequestState(RequestState requestState) {
Gson gson = new Gson();
return gson.toJson(requestState);
}
#TypeConverter
public static RequestState toRequestState(String requestStateString) {
Gson gson = new Gson();
return gson.fromJson(requestStateString, RequestState.class);
}
...
}
My question is; is it okay and acceptable to have a setup like this? If I have many rows which are fetched using a Room DAO, will each row call an associated TypeConverter? Doesn't that add quite a bit of overhead because of creating a Gson instance each time?
Now, I do not like this setup. It feels like unnecessary instantiation. To avoid it, I ended up with a Singleton Gson wrapper. I can access it from anywhere and it's more or less a util.
public class DeSerializer {
//Added formatters; please ignore the ignorance towards proper localization
private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("dd, MMM, yyyy", Locale.ENGLISH);
private static final SimpleDateFormat TIME_FORMAT = new SimpleDateFormat("hh:mm a", Locale.ENGLISH);
private static final SimpleDateFormat DATETIME_FORMAT = new SimpleDateFormat("dd, MMM, yyyy, hh:mm a", Locale.ENGLISH);
private static final DecimalFormat DISTANCE_FORMAT = new DecimalFormat("#.00 km");
//Singleton gson instance
private static Gson mGson;
public static Gson gson() {
if(mGson == null) {
synchronized (DeSerializer.class) {
if(mGson == null) {
mGson = new GsonBuilder()
.registerTypeAdapter(AddressData.class, new AddressDataAdapter())
.registerTypeAdapter(ImageData.class, new ImageDataAdapter())
.registerTypeAdapter(CollectionData.class, new CollectionDataAdapter())
.create();
}
}
}
return mGson;
}
//Gson wrapper methods
public static String toJson(Object src) {
return gson().toJson(src);
}
public static String toJson(Object src, Type typeOfSrc) {
return gson().toJson(src, typeOfSrc);
}
public static <T> T fromJson(String json, Class<T> classOfT) throws JsonSyntaxException {
return gson().fromJson(json, classOfT);
}
public static <T> T fromJson(String json, Type typeOfT) throws JsonSyntaxException {
return gson().fromJson(json, typeOfT);
}
public static <T> T fromJson(String json, TypeToken<T> typeTokenOfT) throws JsonSyntaxException {
return fromJson(json, typeTokenOfT.getType());
}
//Additional utility; please ignore the blurred boundaries of single responsibility
public static JsonObject jsonWebTokenPayload(String token) {
String[] parts = token.split("\\.");
String json = new String(Base64.decode(parts[1], Base64.URL_SAFE));
return gson().fromJson(json, JsonObject.class);
}
public static <T extends Parcelable> T getParcelable(Bundle data, String key, ClassLoader loader) {
data.setClassLoader(loader);
return data.getParcelable(key);
}
public static String dateString(Date date) {
return DATE_FORMAT.format(date);
}
public static String timeString(Date date) {
return TIME_FORMAT.format(date);
}
public static String datetimeString(Date date) {
return DATETIME_FORMAT.format(date);
}
public static String distanceString(Number distance) {
return DISTANCE_FORMAT.format((double) distance / 1000);
}
}
Then, in the Converters class, I can use DeSerializer instead of having to instantiate a relevant Gson instance each time.
Except Singletons are supposedly evil. When I say supposedly, I mean that due to my lack of understanding, I am unable to see why this setup is bad. I mean, I know it's not the best solution, or even decent, but I'm unable to think of something else without using singleton as a crutch.
DeSerializer is not a proper dependency, and, in my ignorance, I want to say that testability is not an issue since I can't see a case where I'd want to fake or stub DeSerializer.
So, the second part of my question is; is this setup acceptable? Or does it break all the rules of clean architecture?
What is recommended in this case, where I can't inject a dependency into Converters but I also don't want to keep having to recreate Gson instances?
This question already has answers here:
How to prevent Gson from expressing integers as floats
(10 answers)
Closed 4 years ago.
I have some JSON string snippets which could look like this:
{label: "My Label"}
{maxlength: 5}
{contact: {name: "John", "age": 5, children: [{"name": "Mary"]}}
etc, i.e. it could be any JSON object with any key names or value types.
Right now I am deserializing doing something pretty simple like this:
final Gson gson = new Gson();
Object newValue = gson.fromJson(stringValue, Object.class);
And this is working for 99% of the use cases. But as is mentioned here, it is converting any integers to doubles.
I'm fine registering a type adapter as is recommended elsewhere. So I wrote the following:
final Gson gson = new GsonBuilder()
.registerTypeAdapter(Object.class, new DoubleToInt())
.create();
Object newValue = gson.fromJson(stringValue, Object.class);
private static class DoubleToInt implements JsonDeserializer<Object>{
#Override
public Object deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
// do my custom stuff here
return json;
}
}
But this isn't working at all. It's like the type adapter is not even getting registered because breakpoints never even hit in the deserialize method.
As the post you link suggested, you should create custom class, so I did and it's working correctly:
public class Test {
public static void main(String[] args) {
final Gson gson = new GsonBuilder()
.registerTypeAdapter(MyClass.class, new DoubleToInt())
.create();
String stringValue = "{contact: {name: \"John\", \"age\": 5, children: [{\"name\": \"Mary\"}]}}";
MyClass newValue = gson.fromJson(stringValue, MyClass.class);
System.out.println(newValue.toString());
}
private static class DoubleToInt implements JsonDeserializer<MyClass> {
#Override
public MyClass deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
// do my custom stuff here
return new MyClass(json);
}
}
}
class MyClass {
private JsonElement element;
MyClass(JsonElement element) {
this.element = element;
}
#Override
public String toString() {
return element.toString();
}
}
In the post you linked, they suggested using a custom class in order to tell what data types you should use instead of using Object.class. Have you tried doing that?
class CustomClass{
String label;
int maxLength;
...
}
Object newValue = gson.fromJson(stringValue, CustomClass.class);
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
When reading a JSON :
{"field":"value"}
into a String field :
public class Test {
private String field;
}
using Gson.fromJson it works as intended and the member String field gets the value "value".
My question is, is there a way to read the same JSON into a custom class so that the custom class object can be constructed with the String value? e.g.
public class Test {
private MyField<String> field;
}
public class MyField<T> {
private T value;
public MyField(T v) {
value = v;
}
}
The reason being the String class is final and cannot be extended, yet I don't want the JSON to be changed into this :
{"field":{"value":"value"}}
If there is a way to extend the String class, it is the best. Otherwise, will need a way for Gson to read string into a custom class that can be constructed by string. Something to do with writing a custom TypeAdapter?
You can use custom JsonDeserializer, JsonSerializer. Here is simple demo version:
static class MyFieldAsValueTypeAdapter<T> implements
JsonDeserializer<MyField<T>>, JsonSerializer<MyField<T>> {
private Gson gson = new Gson();
#Override
public MyField<T> deserialize(JsonElement json, Type typeOfT,
JsonDeserializationContext context)
throws JsonParseException {
JsonObject obj = new JsonObject();
obj.add("value", json);
return gson.fromJson(obj, typeOfT);
}
#Override
public JsonElement serialize(MyField<T> src, Type typeOfSrc,
JsonSerializationContext context) {
return context.serialize(src.getValue());
}
}
public static void main(String[] args) {
GsonBuilder b = new GsonBuilder();
b.registerTypeAdapter(MyField.class , new MyFieldAsValueTypeAdapter());
Gson gson = b.create();
String json = "{\"field\":\"value1\"}";
Test test = gson.fromJson(json, Test.class);
}
Be careful with internal Gson gson = new Gson(). If you have some other setup, you will need to register it on internal version or pass default MyField deserializer/serializer to your custom implementation.