0

I am using GSON to parse JSON data into Java and I am running into the error that is stated in the title. I am working with an API that returns the following JSON data :

{
  "STATUS": "SUCCESS",
  "NUM_RECORDS": "5",
  "MESSAGE": "5 records found",
  "AVAILABILITY_UPDATED_TIMESTAMP": "2015-05-03T13:59:08.541-07:00",
  "AVAILABILITY_REQUEST_TIMESTAMP": "2015-05-03T13:59:08.490-07:00",
  "AVL": [
    {
      "TYPE": "ON",
      "BFID": "205052",
      "NAME": "5th St (500-598)",
      "RATES": {
        "RS": [
          {
            "BEG": "12:00 AM",
            "END": "12:00 PM",
            "RATE": "0",
            "RQ": "No charge"
          },
          {
            "BEG": "12:00 PM",
            "END": "6:00 PM",
            "RATE": "5",
            "RQ": "Per hour"
          },
          {
            "BEG": "6:00 PM",
            "END": "12:00 AM",
            "RATE": "0",
            "RQ": "No charge"
          }
        ]
      },
      "PTS": "2",
      "LOC": "-122.4002212834,37.7776161738,-122.3989619795,37.7766113458"
    },
    {
      "TYPE": "ON",
      "BFID": "205042",
      "NAME": "5th St (450-498)",
      "RATES": {
        "RS": {
          "BEG": "12:00 AM",
          "END": "12:00 AM",
          "RATE": "0",
          "RQ": "No charge"
        }
      },
      "PTS": "2",
      "LOC": "-122.4015027158,37.7786330718,-122.4005149869,37.7778485214"
    },
  ]
}

I can see where the problem occurs, the RS field can either contain an array of objects (let's call this object RInfo) or in some cases it will only contain one of that RInfo object that is not contained in an array. I think that the error occurs because GSON is looking for an array but found an object. I am unable to change the structure of the JSON file because it was provided by an API.

I am able to parse the information successfully as long as RS is an array of RInfo objects but in some cases RS contains only one RInfo object so this error occurs.

Is there a way to handle this in GSON?

*Update

I have tried a solution that was linked earlier. Here is what I have from that solution:

class RSDeserializer implements JsonDeserializer<RateInfo[]> {

    @Override
    public RateInfo[] deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
            throws JsonParseException
    {


        if (json instanceof JsonArray) {
            System.out.println("fromJson in RSD:" + new Gson().fromJson(json, RateInfo[].class));
            return new Gson().fromJson(json, RateInfo[].class);
        }
        RateInfo rI = context.deserialize(json, RateInfo.class);

        return new RateInfo[] { rI };
    }

}

I have also created a new GsonBuilder as follows

    GsonBuilder gsonBuilder = new GsonBuilder();
    gsonBuilder.registerTypeAdapter(RateInfo[].class, new RSDeserializer());
    Gson gson = gsonBuilder.create();

It doesn't seem like the custom deserializer is ever used because the print statement was never printed out in the console. After that I have tried to deserilize the json using MyOBJ info = gson.fromJson(json, MyOBJ.class); this line gives me the Expected BEGIN_ARRAY but was BEGIN_OBJECT exception.

Zhedar
  • 3,480
  • 1
  • 21
  • 44
Rowen McDaniel
  • 169
  • 1
  • 2
  • 11
  • I have tried that solution but couldn't get it to work. Does custom deserialization work on more complicated Json files such as the one I am working with? – Rowen McDaniel May 03 '15 at 22:05
  • Yes, works fine with more complex JSONs. Any error with your custom solution? – Bruno Ribeiro May 03 '15 at 22:06
  • I have updated my initial post with what I have tried. It doesn't seem like the custom deserializer is being used. – Rowen McDaniel May 03 '15 at 22:12
  • 1
    Jackson has [a built in feature](http://fasterxml.github.io/jackson-databind/javadoc/2.2.0/com/fasterxml/jackson/databind/DeserializationFeature.html#ACCEPT_SINGLE_VALUE_AS_ARRAY) to support this, I'm surprised Gson doesn't have the same. – Ian Roberts May 04 '15 at 00:17
  • I've tried your deserializer and it works well for me. You can check this simple gist: https://gist.github.com/alexcrt/3bdae1589b320b6405e1 – Alexis C. May 04 '15 at 06:27
  • @AlexisC. For some reason it works with `Array` but doesn't work with `ArrayList`. I'm not really sure why... – Rowen McDaniel May 04 '15 at 06:31
  • @RowenMcDaniel It works also fine for me when I deserialize it into an `ArrayList`. Please give me a pastebin of the gist I published with your modifications when you try to serialize it into a `List`. – Alexis C. May 04 '15 at 06:46
  • @AlexisC. Here is the pastebin of the code I am trying to run http://pastebin.com/F3Yss5Qm . It gives an `IllegalStateException` saying Expected BEGIN_ARRAY but was BEGIN_OBJECT at line 1 column 274 path $.AVL[0].RATES.RS. – Rowen McDaniel May 04 '15 at 07:02
  • @RowenMcDaniel Not surprising it doesn't work if you don't update the deserializer to return an `ArrayList`... and same when you register it in your `GsonBuilder`. – Alexis C. May 04 '15 at 07:08
  • @AlexisC. I thought that the type in the `< >` was the type GSON searches for in the Json data? I am not exactly sure how I would update the deserializaer to return an `ArrayList`. Would I just change `RateInfo[]` to `ArrayList`? – Rowen McDaniel May 04 '15 at 07:19
  • @RowenMcDaniel Yes. And since generics are erased at runtime, you'd need to use a `TypeToken` when calling `fromJson` and registering your adapter: `new TypeToken>(){}.getType()` – Alexis C. May 04 '15 at 07:20
  • @AlexisC. Could you give me an example using `TypeToken`? I am not really sure I am understanding you. – Rowen McDaniel May 08 '15 at 00:34

1 Answers1

1

You have to update the deserializer to tell that you want to return a List<RateInfo>:

class RSDeserializer implements JsonDeserializer<List<RateInfo>> {
    @Override
    public List<RateInfo> deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
        if (json instanceof JsonArray) {
            return Arrays.asList(context.deserialize(json, RateInfo[].class));
        }
        return Collections.singletonList(context.deserialize(json, RateInfo.class));
    }
}

and you need to also tell to the parser that this is the type you want to deserialize too when registering this adapter:

Type t = new TypeToken<List<RateInfo>>(){}.getType();
Gson gson = new GsonBuilder().registerTypeAdapter(t, new RSDeserializer()).create();

A full working example can be found in my gist.

Alexis C.
  • 91,686
  • 21
  • 171
  • 177