0

I've got an object, which I need deserialized / unmarshalled from JSON (using Gson). My problem is that one of the properties, in my JavaBean, is a complex object, but needs to be serialized fron a JSON array of numbers:

The JSON is (somewhat similar to):

{
    "name": "John Doe",
    "location": [
        9.560006,
        55.719474
    ],
}

The targeted classes are (somwaht similar to):

class UserLocation {
    private String name;
    private Location location;
    // ... "zero-args constructor", get'ers and set'ers
}
class Location {
    private double longitude;
    private double latitude;
    // ... "zero-args constructor", get'ers and set'ers
}

My GsonBuilder is configured as:

new GsonBuilder()
    .registerTypeAdapter(Location.class, new LocationTypeConverter())
    .create();

Where my LocationTypeConverter looks like this:

public class LocationTypeConverter extends TypeAdapter<Location> implements JsonSerializer<Location>, JsonDeserializer<Location> {
    private static final int LONGITUDE_INDEX = 0;
    private static final int LATITUDE_INDEX = 1;

    @Override
    public JsonElement serialize(Location src, Type srcType, JsonSerializationContext context) {
        JsonArray array = new JsonArray();
        array.set(LATITUDE_INDEX, new JsonPrimitive(src.getLatitude()));
        array.set(LONGITUDE_INDEX, new JsonPrimitive(src.getLongitude()));
        return array;
    }

    @Override
    public Location deserialize(JsonElement json, Type type, JsonDeserializationContext context) throws JsonParseException {
        JsonArray array = json.getAsJsonArray();
        return new Location(array.get(LATITUDE_INDEX).getAsDouble(), array.get(LONGITUDE_INDEX).getAsDouble());
    }

    @Override
    public void write(JsonWriter out, Location value) throws IOException {
        out.beginArray().value(value.getLongitude()).value(value.getLatitude()).endArray();
    }

    @Override
    public Location read(JsonReader in) throws IOException {
        in.beginArray();
        double longitude = in.nextDouble();
        double latitude = in.nextDouble();
        in.endArray();
        return new Location(longitude, latitude);
    }
}

But to no avail, since I keep recieving the following Stacktrace:

     retrofit.RetrofitError: com.google.gson.JsonSyntaxException: java.lang.IllegalStateException: Expected BEGIN_OBJECT but was BEGIN_ARRAY at line 1 column 85 path $[0].location
            at retrofit.RestAdapter$RestHandler.invokeRequest(RestAdapter.java:377)
            at retrofit.RestAdapter$RestHandler.access$100(RestAdapter.java:220)
            at retrofit.RestAdapter$RestHandler$2.obtainResponse(RestAdapter.java:278)
            at retrofit.CallbackRunnable.run(CallbackRunnable.java:42)
            at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1112)
            at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:587)
            at retrofit.Platform$Android$2$1.run(Platform.java:142)
            at java.lang.Thread.run(Thread.java:818)
     Caused by: retrofit.converter.ConversionException: com.google.gson.JsonSyntaxException: java.lang.IllegalStateException: Expected BEGIN_OBJECT but was BEGIN_ARRAY at line 1 column 85 path $[0].location
            at retrofit.converter.GsonConverter.fromBody(GsonConverter.java:67)
            at retrofit.RestAdapter$RestHandler.invokeRequest(RestAdapter.java:362)
            at retrofit.RestAdapter$RestHandler.access$100(RestAdapter.java:220)
            at retrofit.RestAdapter$RestHandler$2.obtainResponse(RestAdapter.java:278)
            at retrofit.CallbackRunnable.run(CallbackRunnable.java:42)
            at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1112)
            at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:587)
            at retrofit.Platform$Android$2$1.run(Platform.java:142)
            at java.lang.Thread.run(Thread.java:818)
     Caused by: com.google.gson.JsonSyntaxException: java.lang.IllegalStateException: Expected BEGIN_OBJECT but was BEGIN_ARRAY at line 1 column 85 path $[0].location
            at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.read(ReflectiveTypeAdapterFactory.java:200)
            at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$1.read(ReflectiveTypeAdapterFactory.java:103)
            at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.read(ReflectiveTypeAdapterFactory.java:196)
            at com.google.gson.internal.bind.TypeAdapterRuntimeTypeWrapper.read(TypeAdapterRuntimeTypeWrapper.java:40)
            at com.google.gson.internal.bind.CollectionTypeAdapterFactory$Adapter.read(CollectionTypeAdapterFactory.java:81)
            at com.google.gson.internal.bind.CollectionTypeAdapterFactory$Adapter.read(CollectionTypeAdapterFactory.java:60)
            at com.google.gson.Gson.fromJson(Gson.java:810)
            at com.google.gson.Gson.fromJson(Gson.java:775)
            at retrofit.converter.GsonConverter.fromBody(GsonConverter.java:63)
            at retrofit.RestAdapter$RestHandler.invokeRequest(RestAdapter.java:362)
            at retrofit.RestAdapter$RestHandler.access$100(RestAdapter.java:220)
            at retrofit.RestAdapter$RestHandler$2.obtainResponse(RestAdapter.java:278)
            at retrofit.CallbackRunnable.run(CallbackRunnable.java:42)
            at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1112)
            at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:587)
            at retrofit.Platform$Android$2$1.run(Platform.java:142)
            at java.lang.Thread.run(Thread.java:818)
     Caused by: java.lang.IllegalStateException: Expected BEGIN_OBJECT but was BEGIN_ARRAY at line 1 column 85 path $[0].location
            at com.google.gson.stream.JsonReader.beginObject(JsonReader.java:387)
            at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.read(ReflectiveTypeAdapterFactory.java:189)

The two classes that needs to be deserialized (the one containing the Location instance and the "wrapping" object itself are both simple JavaBean (with appropriate set'er and get'er methods.

None of the methods serialize, deserialize nor read are being called, so what am I doing wrong here?

Thanks in advance, for your help!

EDIT #1: Added target classses / beans to clarify the object graph I'm trying to deserialize to.

Anders
  • 660
  • 3
  • 12
  • I don't think you need a special `registerTypeAdapter` for this. Just create a `Location.class` with a `String` and a `List` – Jared Burrows Jul 23 '15 at 11:42
  • possible duplicate of [GSON throwing "Expected BEGIN\_OBJECT but was BEGIN\_ARRAY"?](http://stackoverflow.com/questions/9598707/gson-throwing-expected-begin-object-but-was-begin-array) – ooozguuur Jul 23 '15 at 11:45
  • @jackk I doubt it's a duplicate - the question you're referring to is in regards to the entire response being an array, and the Gson deserialization mechanism is targeted towards an object. My question is in regard to parsing a single property of an object. – Anders Jul 23 '15 at 11:49

1 Answers1

2

This is the class that you need to ser/des the JSON that you wrote:

MyObject {
    public String name;
    public float[] location;

    public float getLatitude() {
        if (location != null) return location[0];
        return 0;
    }
    public float getLongitude() {
        if (location != null) return location[1];
        return 0;
    }
}

while the Classes that you wrote will have a JSON like this:

{
    "name": "John Doe",
    "location": {
        "longitude": 9.560006,
        "latitude": 55.719474
    }
}

And finally, this is the test I did with your code, and it works fine! Probably you just need to be sure you're using always the same Gson object (the one you get from GsonBuilder.create)?

    Gson gson = new GsonBuilder()
            .registerTypeAdapter(Location.class, new LocationTypeConverter())
            .setPrettyPrinting()
            .create();

    UserLocation userLocation = new UserLocation();
    userLocation.name = "ciao";
    userLocation.location = new Location(1.0, 3.0);
    String json = gson.toJson(userLocation);
    System.out.println(json);

    UserLocation newlocation = gson.fromJson(json, UserLocation.class);
    System.out.print(newlocation.location.getLatitude());
Mimmo Grottoli
  • 5,758
  • 2
  • 17
  • 27
  • Sorry, I forgot all about posting my "target" object graph... I already have that (so I posted an update)... Thanks for your feedback, though. – Anders Jul 23 '15 at 11:55
  • 1
    Sorry, but I really want a (Complex) object, instead of the float array, since multiple objects in my Application depends on the behaviour of my Location class. The Location class also has distanceTo methods, etc (just following sound OOP-principles). – Anders Jul 23 '15 at 12:04