0

I have a json that looks like this:

[
{
_id: "54b8f62fa08c286b08449b8f",
loc: [
   36.860983,
   31.0567
]
},
{
_id: "54b8f6aea08c286b08449b93",
loc: {
coordinates: [ ]
}
}
]

As you can see, loc object is sometimes is a json object, sometimes is a double array. Without writing a custom deserializer, is there a way to avoid JsonSyntaxException and set the loc object to null when it is a json object rather than a double array.

osrl
  • 8,168
  • 8
  • 36
  • 57
  • Well, I don't think so Devrim Bey :) I've already managed this by using a custom deserializer; set object to null when I catch the exception (Maybe TypeAdapter is better approach). I'm looking for something a little bit easier like `new GsonBuilder().ignoreExceptions().create()` or something like this. – osrl Feb 16 '15 at 19:31

2 Answers2

2

There aren't any easy way (I mean a property/method call at Gson) for custom seralization/deserialization of a specific field at a json value.

You can see source code of com.google.gson.internal.bind.ReflectiveTypeAdapterFactory, and debug on its inner class Adapter's read method. (That's where your JsonSyntaxException occurs)

You can read Custom serialization for JUST specific fields and track its links. It may be implemented at future release of Gson. (Not available at latest release 2.2.4)

I would write some code for this. Maybe that's not what you are looking for but it may help somebody else.)

Solution 1 (This has less code compared with the second solution but second solution's performance is much more better):

public class SubClass extends BaseClass {
    private double[] loc;
}

public class BaseClass {
    @SerializedName("_id")
    private String id;
}

public class CustomTypeAdapter extends TypeAdapter<BaseClass> {
    private Gson gson;
    public CustomTypeAdapter() {
        this.gson = new Gson();
    }

    @Override
    public void write(JsonWriter out, BaseClass value)
            throws IOException {
        throw new RuntimeException("Not implemented for this question!");
    }

    @Override
    public BaseClass read(JsonReader in) throws IOException {
        BaseClass instance;
        try {
            instance = gson.fromJson(in, SubClass.class);
        } catch (Exception e) {
            e.printStackTrace();
            instance = gson.fromJson(in, BaseClass.class);
        }
        return instance;
    }
}

Test:

private void test() {
    String json = "[{_id:\"54b8f62fa08c286b08449b8f\",loc:[36.860983,31.0567]},{_id:\"54b8f6aea08c286b08449b93\",loc:{coordinates:[]}}]";

    Type collectionType = new TypeToken<List<BaseClass>>(){}.getType();
    Gson gson = new GsonBuilder().registerTypeAdapter(BaseClass.class, new CustomTypeAdapter()).create();
    List<BaseClass> list = gson.fromJson(json, collectionType);

    for(BaseClass item : list) {
        if(item instanceof SubClass) {
            System.out.println("item has loc value");
            SubClass subClassInstance = (SubClass)item;
        } else {
            System.out.println("item has no loc value");
            BaseClass baseClassInstance = item;
        }
    }
}

Solution 2 (It is one of the Gson Developers suggestion. See original post.):

Copy below class to your project. It is going to be a base class for your custom TypeAdapterFactorys.

public abstract class CustomizedTypeAdapterFactory<C>
        implements TypeAdapterFactory {
    private final Class<C> customizedClass;

    public CustomizedTypeAdapterFactory(Class<C> customizedClass) {
        this.customizedClass = customizedClass;
    }

    @SuppressWarnings("unchecked") // we use a runtime check to guarantee that 'C' and 'T' are equal
    public final <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
        return type.getRawType() == customizedClass
                ? (TypeAdapter<T>) customizeMyClassAdapter(gson, (TypeToken<C>) type)
                : null;
    }

    private TypeAdapter<C> customizeMyClassAdapter(Gson gson, TypeToken<C> type) {
        final TypeAdapter<C> delegate = gson.getDelegateAdapter(this, type);
        final TypeAdapter<JsonElement> elementAdapter = gson.getAdapter(JsonElement.class);
        return new TypeAdapter<C>() {
            @Override public void write(JsonWriter out, C value) throws IOException {
                JsonElement tree = delegate.toJsonTree(value);
                beforeWrite(value, tree);
                elementAdapter.write(out, tree);
            }
            @Override public C read(JsonReader in) throws IOException {
                JsonElement tree = elementAdapter.read(in);
                afterRead(tree);
                return delegate.fromJsonTree(tree);
            }
        };
    }

    /**
     * Override this to muck with {@code toSerialize} before it is written to
     * the outgoing JSON stream.
     */
    protected void beforeWrite(C source, JsonElement toSerialize) {
    }

    /**
     * Override this to muck with {@code deserialized} before it parsed into
     * the application type.
     */
    protected void afterRead(JsonElement deserialized) {
    }
}

Write your POJO and your custom CustomizedTypeAdapterFactory. Override afterRead method and handle double array as you asked at your question:

public class MyClass {
    @SerializedName("_id")
    private String id;
    private double[] loc;
    // getters/setters
}

private class MyClassTypeAdapterFactory extends CustomizedTypeAdapterFactory<MyClass> {
    private MyClassTypeAdapterFactory() {
        super(MyClass.class);
    }

    @Override protected void afterRead(JsonElement deserialized) {
        try {
            JsonArray jsonArray = deserialized.getAsJsonObject().get("loc").getAsJsonArray();

            System.out.println("loc is not a double array, its ignored!");
        } catch (Exception e) {
            deserialized.getAsJsonObject().remove("loc");
        }
    }
}

Test:

private void test() {
    String json = "[{_id:\"54b8f62fa08c286b08449b8f\",loc:[36.860983,31.0567]},{_id:\"54b8f6aea08c286b08449b93\",loc:{coordinates:[]}}]";

    Gson gson = new GsonBuilder()
            .registerTypeAdapterFactory(new MyClassTypeAdapterFactory())
            .create();

    Type collectionType = new TypeToken<List<MyClass>>(){}.getType();
    List<MyClass> list = gson.fromJson(json, collectionType);

    for(MyClass item : list) {
        if(item.getLoc() != null) {
            System.out.println("item has loc value");
        } else {
            System.out.println("item has no loc value");
        }
    }
}
Community
  • 1
  • 1
Devrim
  • 15,345
  • 4
  • 66
  • 74
  • That's the accepted answer since it is the best way to achieve my goal. Please check my answer. – osrl Feb 16 '15 at 23:51
0

This is how I did this. It is shorter, but I think @DevrimTuncers answer is the best one.

//This is just Double array to use as location object
public class Location extends ArrayList<Double> {
    public Double getLatidute() {
        if (this.size() > 0) {
            return this.get(0);
        } else {
            return (double) 0;
        }
    }

    public Double getLongitude() {
        if (this.size() > 1) {
            return this.get(1);
        } else {
            return (double) 0;
        }
    }

    public static class LocationDeserializer implements JsonDeserializer<Location> {
        @Override
        public Location deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
            try {
                JsonArray array = json.getAsJsonArray();
                Location location = new Location();
                for (int i = 0; i < array.size(); i++) {
                    location.add(array.get(i).getAsDouble());
                }

                return location;
            } catch (Exception e) {
                return null;
            }
        }
    }
}
osrl
  • 8,168
  • 8
  • 36
  • 57