Related
In a request to the Reddit API, there is a field associated with each post called edited. This field either has a boolean value or if a post has been edited, has a long value which I think is the timestamp of when the post was edited. How can I deserialize this with GSON without knowing the type? If I try to deserialize into a Boolean value I get an exception if a timestamp is present.
Image below of JSON response:
Annotating the dynamic field with #JsonAdapter is probably the easiest way to work around this (assuming you have a DTO class):
final class Datum {
#JsonAdapter(MaybeLongTypeAdapter.class)
final Long edited = null;
}
Where MaybeLongTypeAdapter is as follows:
final class MaybeLongTypeAdapter
extends TypeAdapter<Long> {
private MaybeLongTypeAdapter() {
}
#Override
public void write(final JsonWriter out, final Long value) {
throw new UnsupportedOperationException();
}
#Override
public Long read(final JsonReader in)
throws IOException {
switch ( in.peek() ) {
case NULL:
return null;
case BOOLEAN:
if ( in.nextBoolean() ) {
throw new JsonSyntaxException("Unexpected `true` at " + in);
}
return null;
case NUMBER:
return in.nextLong();
default:
throw new JsonSyntaxException("Unexpected element at " + in);
}
}
}
The type adapter above is pretty self-descriptive. It can be implemented in a more generic way, of course, but it's out of scope here. Additionally, please note that it does not pick the original Long type adapter that can be re-configured in GsonBuilder. Example of use:
private static final Gson gson = new Gson();
private static final Type listOfDatumType = new TypeToken<List<Datum>>() {}.getType();
public static void main(final String... args) {
final String json = "[{\"edited\": false},{\"edited\": 1527130582}]";
final List<Datum> data = gson.fromJson(json, listOfDatumType);
for ( final Datum datum : data ) {
System.out.println(datum.edited);
}
}
Output:
null
1527130582
I am currently using json for my code to retrieve an array of boolean values. However, i am adding a subfield to achieve another array within, but I am not good with json and kind of stuck how to go about it.
Here's my code so far:
field values :
public enum Field {
/**
* Runtime Config Fields
**/
FIELD_CAN_CHANGE_PASSWORD("canChangePassword", true, canUpdate),
FIELD_MAX_AUTO_DOWNLOAD_SIZE("maxAutoDownloadSize", 5000000L),
FIELD_ALWAYS_REAUTHENTICATE("alwaysReauthenticate", false, canUpdate),
FIELD_CAN_START_CALL("canStartCall", false),
FIELD_ROOMS_ENABLED("roomsEnabled", !Core.isMessenger()),
FIELD_CAN_CREATE_ROOM("canCreateRoom", !Core.isMessenger(), canUpdate),
FIELD_MAX_ENVELOPE_TTL("maxTTL", Core.isMessenger() ? 518400L : 31536000L, canUpdate),
FIELD_MAX_BURN_ON_READ_TTL("maxBOR", 0L, canUpdate),
FIELD_MAX_UPLOAD_SIZE("maxUploadSize", -1L, true),
FIELD_FRIEND_FINDER("friendFinder", !Core.isEnterprise(), canUpdate),
FIELD_ONLY_SHOW_IN_NETWORK_CONTACTS("onlyShowInNetwork", false),
FIELD_CAN_ADD_CONTACT("canAddContact", true, canUpdate),
FIELD_FORCE_DEVICE_LOCKOUT("forceDeviceLockout", 5L, canUpdate),
FIELD_VERIFICATION_MODE("verificationMode", VerificationMode.OPTIONAL.getValue(), true),
FIELD_ENABLE_NOTIFICATION_PREVIEW("enableNotificationPreview", true, true),
FIELD_DIRECTORY_ENABLED("directoryEnabled", true, true);
public String fieldName;
public Object defaultValue;
public boolean updateFromServer;
Field(String key, Object defaultValue) {
this(key, defaultValue, true);
}
Field(String key, Object defaultValue, boolean updateFromServer) {
this.fieldName = key;
this.defaultValue = defaultValue;
this.updateFromServer = updateFromServer;
}
}
putting values in field:
private void putValueForField(JSONObject configuration, Field field) {
try {
if (configuration.isNull(field.fieldName)) {
Object value = field.defaultValue;
if (value instanceof long[]) {
JSONArray array = new JSONArray();
for (long obj : (long[]) field.defaultValue) {
array.put(obj);
}
value = array;
}
runtimeConfiguration.put(field.fieldName, value);
} else {
runtimeConfiguration.put(field.fieldName, configuration.get(field.fieldName));
}
} catch (JSONException e) {
}
}
getting values :
private Object getValueForField(Field field) {
if (runtimeConfiguration.has(field.fieldName) && field.updateFromServer) {
try {
Object value = runtimeConfiguration.get(field.fieldName);
if (value instanceof JSONArray) {
JSONArray values = (JSONArray) value;
if (values.get(0) instanceof Number) {
long[] retVals = new long[values.length()];
for (int i = 0; i < values.length(); i++) {
retVals[i] = ((Number) values.get(i)).longValue();
}
return retVals;
}
} else if (value instanceof Number) {
return ((Number) value).longValue();
} else {
return value;
}
} catch (Exception e) {
e.printStackTrace();
}
}
return field.defaultValue;
}
one of the methods using the fields above:
public boolean canChangePassword() {
return (boolean) getValueForField(Field.FIELD_CAN_CHANGE_PASSWORD);
}
My new json is :
{"enableNotificationPreview":true,"destructOnRead":[30,60,300],"alwaysReauthenticate":false,"forceDeviceLockout":0,"permmod":1516894585,"maxBOR":0,"roomsEnabled":true,"directoryEnabled":true,"canStartCall":true,"canAddContact":true,"legacyDownload":false,"verificationMode":1,"restrictedAdmin":false,"canChangePassword":true,"friendFinder":true,"NEWVALUE":{"canStartNewValue1":true,"canStartroupValue":true,"canVideoCall":true,"canStartRoomValue":true,"canAddtoValue":true,"canStartValueshare":true},"canCreateRoom":true,"maxTTL":2592000,"onlyShowInNetwork":false,"maxUploadSize":null,"availableEnvelopeTTL":[0,600,3600,86400,604800,2592000],"maxAutoDownloadSize":7340032}
where I am plugging in :
"NEWVALUE":{"canStartNewValue1":true,"canStartroupValue":true,"canVideoCall":true,"canStartRoomValue":true,"canAddtoValue":true,"canStartValueshare":true}
Not sure how to update my putValueForField to reflect this new json and the corresponding fields. Any idea?
To check and retrieve JSONObject from the json object you can modify your method to check for instance of JSONObject and assign value to it.
private void putValueForField(JSONObject configuration, Field field) {
try {
if (configuration.isNull(field.fieldName)) {
Object value = field.defaultValue;
if (value instanceof long[]) {
JSONArray array = new JSONArray();
for (long obj : (long[]) field.defaultValue) {
array.put(obj);
}
value = array;
}
// this is how you check if fieldName is of type jsonobject
if (value instanceof JSONObject) {
JSONObject valueObject = configuration.getJSONObject(field.fieldName);
value = valueObject;
}
runtimeConfiguration.put(field.fieldName, value);
} else {
runtimeConfiguration.put(field.fieldName, configuration.get(field.fieldName));
}
} catch (JSONException e) {
}
}
Form what I can understand you are just getting the values from JSON and saving it in field in that case:
I think in this method ultimately you are just storing all values as object only so while retrieving you have to do all this check again.
If you are getting the response from server with fixed Object fields and object values you should be creating a class from that configuration where you will know what exactly it will return. That would really make life easier.
Or maybe if you just need the configuration to get/update or put values you can maintain it in json object itself Android JSON doc reference
For example lets say you have this jsonobject itself, I have added the working example below, hope this helps.
import org.json.*;
class Main {
public static void main(String[] args) throws JSONException {
String jsn = "{\"enableNotificationPreview\":true,\"destructOnRead\":[30,60,300],\"" +
"alwaysReauthenticate\":false,\"forceDeviceLockout\":0,\"NEWVALUE\":{\"canStartNewValue1\":true," +
"\"canStartroupValue\":true,\"canVideoCall\":true,\"canStartRoomValue\":true},\"canCreateRoom\":true," +
"\"maxTTL\":2592000,\"onlyShowInNetwork\":false,\"maxUploadSize\":null,\"availableEnvelopeTTL\"" +
":[0,600,3600,86400,604800,2592000],\"maxAutoDownloadSize\":7340032}";
JSONObject jsnObj = new JSONObject(jsn);
Object enableNotificationPreview = jsnObj.get("enableNotificationPreview");
// or if you know it will be boolean
boolean enableNotificationPreviewBool = jsnObj.getBoolean("enableNotificationPreview");
System.out.println(enableNotificationPreview);
// output : true
System.out.println(enableNotificationPreviewBool);
// output : true
JSONObject NEWVALUEObj = jsnObj.getJSONObject("NEWVALUE");
// now again you can extract values from it
boolean canStartNewValue1 = NEWVALUEObj.getBoolean("canStartNewValue1");
System.out.println(canStartNewValue1);
NEWVALUEObj.put("canStartNewValue1", false);
System.out.println(NEWVALUEObj.toString());
jsnObj.put("forceDeviceLockout", 23456);
// now canStartNewValue1 value is changed to false in the object
// and forceDeviceLockout is also 23456
System.out.println(jsnObj.toString());
}
}
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 am using GSON in my android app. Currently, I am setting a method that contains a value from my JSON string called "followed". One of items in the JSON string contains followed and the second string does not. I use Realm to persist the unique object so as you can see it just gets overwritten.
Here are 2 json strings to compare as an example:
{"customer_case":"OFFICE001","circle_id":"3","address":"10 Canal St","city":"Bristol","state":"PA","zip":"19007","county":"Bucks County","apt_no":"","latitude":"40.1012666","longitude":"-74.855304","profile_picture":"uploads/thumbnails/2014/06/07/16/1402165202_3_16_539356ad9134b3.jpg","id":"539356ad9134b3","google_address":"10 Canal Street","google_city":"Bristol","google_state":"Pennsylvania","verified_zip":"19007","google_county":"Bucks County","status":"Active","add_date":"2014-06-07","circle_name":"Test Portfolio","step":"Rental","loan":"","winterized":null,"boiler":null,"sump_pump":null,"septic":null,"police_id":null,"police":null,"police_phone":null,"electric_id":null,"electric":null,"electric_phone":null,"sewer_id":null,"sewer":null,"sewer_phone":null,"water_id":null,"water":null,"water_phone":null,"fsm_company_id":"5","fsm_company":"Assero Services LLC - FSM","fsm_email":"leemertins#assero24.com","fsm_phone":"2155868317","hoa_id":null,"hoa":null,"hoa_email":null,"hoa_phone":null,"client_id":"9","client":"Test Client","client_email":"krishna162#gmail.com","client_phone":"2157830782","broker_contact_id":null,"broker":null,"broker_email":null,"broker_phone":null,"lawn_contractor":null,"cleaning_contractor":null,"bedroom":null,"bathroom":null,"sqft":null,"lot_size":null,"list_price":"538525","built":null,"assign_date":"06/07/2014","lock_box":null,"gate_code":null,"key_code":null,"property_type":"Unknown","description":null,"sub_status":null,"occupancy_status":null,"street_view":"uploads/2015/06/25/4036/0470e4cd-ce9d-4439-8031-6be5101cd09c.JPG","marketing_front":"uploads/2015/06/25/4036/b099a190-f354-454a-8479-bec67bc41988.JPG","followed":"1"}
{"customer_case":"OFFICE001","circle_id":"3","address":"10 Canal St","city":"Bristol","state":"PA","zip":"19007","county":"Bucks County","apt_no":"","latitude":"40.1012666","longitude":"-74.855304","profile_picture":"uploads/thumbnails/2014/06/07/16/1402165202_3_16_539356ad9134b3.jpg","id":"539356ad9134b3","google_address":"10 Canal Street","google_city":"Bristol","google_state":"Pennsylvania","verified_zip":"19007","google_county":"Bucks County","status":"Active","add_date":"2014-06-07","circle_name":"Test Portfolio","step":"Rental","loan":"","winterized":null,"boiler":null,"sump_pump":null,"septic":null,"police_id":null,"police":null,"police_phone":null,"electric_id":null,"electric":null,"electric_phone":null,"sewer_id":null,"sewer":null,"sewer_phone":null,"water_id":null,"water":null,"water_phone":null,"fsm_company_id":"5","fsm_company":"Assero Services LLC - FSM","fsm_email":"leemertins#assero24.com","fsm_phone":"2155868317","hoa_id":null,"hoa":null,"hoa_email":null,"hoa_phone":null,"client_id":"9","client":"Test Client","client_email":"krishna162#gmail.com","client_phone":"2157830782","broker_contact_id":null,"broker":null,"broker_email":null,"broker_phone":null,"lawn_contractor":null,"cleaning_contractor":null,"bedroom":null,"bathroom":null,"sqft":null,"lot_size":null,"list_price":"538525","built":null,"assign_date":"06/07/2014","lock_box":null,"gate_code":null,"key_code":null,"property_type":"Unknown","description":null,"sub_status":null,"occupancy_status":null,"street_view":"uploads/2015/06/25/4036/0470e4cd-ce9d-4439-8031-6be5101cd09c.JPG","marketing_front":"uploads/2015/06/25/4036/b099a190-f354-454a-8479-bec67bc41988.JPG"}
Note the difference is the followed item at the end of the json string.
From the GSON documentation it says:
"While deserialization, a missing entry in JSON results in setting the corresponding field in the object to null."
Is there a way to override this and not automatically set it to null, instead just skip the field?
Here is some code that I am using to deserialize my json:
PropertyObject prop = visnetawrap.gsonClient.fromJson(properties.get(i).toString(), PropertyObject.class);
visnetawrap.gsonClient = new GsonBuilder()
.setExclusionStrategies(new ExclusionStrategy() {
#Override
public boolean shouldSkipField(FieldAttributes f) {
return f.getDeclaringClass().equals(RealmObject.class) || f.getDeclaredClass().equals(Drawable.class);
}
#Override
public boolean shouldSkipClass(Class<?> clazz) {
return false;
}
})
.registerTypeAdapter(Date.class, new GsonDateDeserializer())
.registerTypeAdapter(Double.class, new TypeAdapter<Double>() {
#Override
public void write(JsonWriter out, Double value) throws IOException {
if (value == null) {
out.nullValue();
return;
}
out.value(value);
}
#Override
public Double read(JsonReader in) throws IOException {
if (in.peek() == JsonToken.NULL) {
in.nextNull();
return null;
}
String stringValue = in.nextString();
try {
return Double.valueOf(stringValue);
} catch (NumberFormatException e) {
return null;
}
}
})
.create();
Here is what I am doing as a work around:
.registerTypeAdapter(PropertyObject.class, new JsonDeserializer<PropertyObject>() {
#Override
public PropertyObject deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
PropertyObject prop = new PropertyObject();
JsonObject propObj = json.getAsJsonObject();
if (propObj.get("id") == null) {
return null;
}
prop.setPropertyId(propObj.get("id").getAsString());
if (propObj.get("followed") == null) {
Realm realmThread = Realm.getDefaultInstance();
PropertyObject existingProp = realmThread.where(PropertyObject.class).equalTo("propertyId", propObj.get("id").getAsString()).findFirst();
if (existingProp == null) {
prop.setPropertyFollowed(0);
}
else {
prop.setPropertyFollowed(existingProp.getPropertyFollowed());
}
realmThread.close();
}
else {
prop.setPropertyFollowed(propObj.get("followed").getAsInt());
}
return prop;
}
})
you may create your own TypeAdapter
public class YourTypeAdapter extends TypeAdapter<PropertyObject> {
#Override
public PropertyObject read(final JsonReader in) throws IOException {
final PropertyObject obj = new PropertyObject(); //I don't know how is your obj
in.beginObject();
boolean hasFollowedField = false;
while (in.hasNext()) {
switch (in.nextName()) {
case "gate_code":
//set value to your obj
obj.setValue(in.nextString())
break;
//do same thing to others...
//...
case "followed":
hasFollowedField = true;
//set value to obj
break;
}
if (!hasFollowedField) {
//set followed value to obj what you want
}
}
in.endObject();
return obj;
}
#Override
public void write(final JsonWriter out, final PropertyObject obj) throws IOException {
out.beginObject();
out.name("gate_code").value(gate_code.getGateCode());
//simple set name and value from obj to JsonWriter
out.endObject();
}
}
and then register the TypeAdapter to your GsonBuilder obj
hope it would help
Dearest Stackoverflowers,
I was wondering if anyone knows how to solve this the best way;
I'm talking to an api which returns a json object like this:
{
"field1": "value1",
"field2": "value2",
"details": {
"nested1": 1,
"nested2": 1
}
In java I have an object (entity) which for example, would have all these fields, but with the details as loose fields, so:
field1, field2, nested1, nested2.
This because It's an android project and I can't just go saving a class with info into my entity since I'm bound to ormlite.
Is there any way to convert the fields flat into my object using GSON? note that I'm using a generic class to convert these right now straight from the API. And I want to store these fields (which contain information as an int). In the same entity.
You can write a custom type adapter to map json value to your pojo.
Define a pojo:
public class DataHolder {
public List<String> fieldList;
public List<Integer> detailList;
}
Write a custom typeAdapter:
public class CustomTypeAdapter extends TypeAdapter<DataHolder> {
public DataHolder read(JsonReader in) throws IOException {
final DataHolder dataHolder = new DataHolder();
in.beginObject();
while (in.hasNext()) {
String name = in.nextName();
if (name.startsWith("field")) {
if (dataHolder.fieldList == null) {
dataHolder.fieldList = new ArrayList<String>();
}
dataHolder.fieldList.add(in.nextString());
} else if (name.equals("details")) {
in.beginObject();
dataHolder.detailList = new ArrayList<Integer>();
} else if (name.startsWith("nested")) {
dataHolder.detailList.add(in.nextInt());
}
}
if(dataHolder.detailList != null) {
in.endObject();
}
in.endObject();
return dataHolder;
}
public void write(JsonWriter writer, DataHolder value) throws IOException {
throw new RuntimeException("CustomTypeAdapter's write method not implemented!");
}
}
Test:
String json = "{\"field1\":\"value1\",\"field2\":\"value2\",\"details\":{\"nested1\":1,\"nested2\":1}}";
GsonBuilder builder = new GsonBuilder();
builder.registerTypeAdapter(DataHolder.class, new CustomTypeAdapter());
Gson gson = builder.create();
DataHolder dataHolder = gson.fromJson(json, DataHolder.class);
Output:
About TypeAdapter:
https://google-gson.googlecode.com/svn/trunk/gson/docs/javadocs/com/google/gson/TypeAdapter.html
http://www.javacreed.com/gson-typeadapter-example/