简体   繁体   中英

Deserializing json that is false or object with Retrofit GsonConverterFactory

I am working with a server that returns json. One of the elements is either an object or false - it it is non exiting. I know this is very poor implementation of server response and there are quite a few such cases, but this is what I have to work with. How can I deal with this situation? If there is an object I successfully deserialze it, if none - i get error - EXPECTED OBJECT FOUND BOOLEAN. Even worse, I do not know where I am gonna meet such situations in future on this project. This is the sample json:

    {
  "course": {
    "id": "47902",
    "course": "3844",
    "group": "1825",
    "teacher": "59502",
    "table": "1447",
    "client": "1",
    "course_id": "3844",
    "description": ""
  },
  "teacher": {
    "id": "59502",
    "post": "0",
    "experience": "",
    "dep_experience": "",
    "rank": "0",
    "online": "1458891283",
    "departments": [
      null
    ]
  },
  "depart": {
    "id": "100",
    "postcode": "",
    "public": "1",
    "alias": "",
    "faculty": "97",
    "client": "1"
  },
  "progress": false,
  "files": [
    {
      "teacher": {
        "id": "59502",
        "code": "53bd7c21ad05b03e",
        "photo": "1"
      },
      "files": [
        {
          "id": "0ffe41e5003ee5c0",
          "owner": "59502",
          "address": "0ffe41e5003ee5c0",
          "type": "f",
          "size": "0",
          "time": "2015-07-10 14:39:15",
          "data": ""
        }
      ]
    }
  ]
}

As you can see progress is false here. Other times it is ordinary object like depart . Deserialization is done by Retrofit 2.

Thanks a lot.

I'm assuming you have a top-level mapping similar to the following one and have configured your Retrofit instance for Gson :

final class Response {

    @SerializedName("progress")
    @JsonAdapter(FalseAsNullTypeAdapterFactory.class)
    final Progress progress = null;

}

final class Progress {

    final String foo = null;

}

Note that the progress property is annotated with the @JsonAdapter annotation: we're assuming this is only place were the progress property can be a boolean (if you have many places like this one, you can either annotate each field with this annotation, or .registerTypeAdapter() via GsonBuilder ; in case of .registerTypeAdapterFactory() the factory must check against known types in order not to "intercept" all types).

Now, here is a type adapter factory to deal with your issue:

final class FalseAsNullTypeAdapterFactory
        implements TypeAdapterFactory {

    // Let Gson instantiate it itself
    private FalseAsNullTypeAdapterFactory() {
    }

    @Override
    public <T> TypeAdapter<T> create(final Gson gson, final TypeToken<T> typeToken) {
        // Get a downstream parser (for simplicity: get the default parser for the given type)
        final TypeAdapter<T> delegateTypeAdapter = gson.getDelegateAdapter(this, typeToken);
        return new TypeAdapter<T>() {
            @Override
            public void write(final JsonWriter out, final T value) {
                throw new UnsupportedOperationException();
            }

            @Override
            public T read(final JsonReader in)
                    throws IOException {
                // Peek whether the next JSON token is a boolean
                if ( in.peek() == BOOLEAN ) {
                    // And take the this JSON token as a boolean value
                    // Is it true?
                    if ( in.nextBoolean() ) {
                        // Then it's not something we can handle -- probably a boolean field annotated with @JsonAdapter(FalseAsNullTypeAdapterFactory.class)?
                        throw new MalformedJsonException("Unexpected boolean marker: true");
                    }
                    // We're assuming it's null
                    return null;
                }
                // If it's not a boolean value, then we just delegate parsing to the original type adapter
                return delegateTypeAdapter.read(in);
            }
        };
    }

}

Now just test it:

try ( final Reader reader = getPackageResourceReader(Q43231983.class, "success.json") ) {
    final Response response = gson.fromJson(reader, Response.class);
    System.out.println(response.progress.foo);
}
try ( final Reader reader = getPackageResourceReader(Q43231983.class, "failure.json") ) {
    final Response response = gson.fromJson(reader, Response.class);
    System.out.println(response.progress);
}

where the given resources are:

  • success.json is {"progress":{"foo": "bar"}} ;
  • failure.json is {"progress":false} .

The output is as follows:

bar
null

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM