How to handle a Boolean when expecting a String in

2019-08-20 19:05发布

问题:

I am currently developing with a 3rd party REST API. I am creating objects for their JSON objects being returned by the API. I am following their documentation where it says certain variables will be Strings. Sometimes when there is no String, it will return False. Sometimes I'm expecting a URL and I get False.

How do I handle this?

This is the API in question https://developers.cannabisreports.com/docs/

Strain Object EXAMPLE: https://ghostbin.com/paste/kgeau In the Strain Object, I get an exception when performing a search. Some of the results of the search have booleans instead of strings/url in the bits of code that are commented out.

Sometimes I get this

"genetics": {
            "names": false,
            "ucpc": false,
            "link": false
        }

Sometimes I could get this

"genetics": {
            "names": "Blueberry x Haze",
            "ucpc": "W74AFGH22Z000000000000000 x 9XVU7WJQCD000000000000000",
            "link": "https:\/\/www.cannabisreports.com\/api\/v1.0\/strains\/9XVU7PTG2P000000000000000\/genetics"
        }

回答1:

When you're in trouble with not a very well designed API, Gson gives you some flexibility while deserializing JSON documents produced by such an API. Despite you have numerous ways to work around this issue (like JsonDeserializer, etc), you mostly like are faced with the false-as-null issue I seem to have seen here already. You can help Gson by marking "wrong" fields:

final class Envelope {

    final Genetics genetics = null;

    @Override
    public String toString() {
        return MoreObjects.toStringHelper(this)
                .add("genetics", genetics)
                .toString();
    }

}
final class Genetics {

    @JsonAdapter(FalseAsNullTypeAdapterFactory.class)
    final String names = null;

    @JsonAdapter(FalseAsNullTypeAdapterFactory.class)
    final String ucpc = null;

    @JsonAdapter(FalseAsNullTypeAdapterFactory.class)
    final URL link = null;

    @Override
    public String toString() {
        return MoreObjects.toStringHelper(this)
                .add("names", names)
                .add("ucpc", ucpc)
                .add("link", link)
                .toString();
    }

}

And this is how the type adapter factory implemented:

final class FalseAsNullTypeAdapterFactory
        implements TypeAdapterFactory {

    // No worries, Gson will instantiate it itself
    private FalseAsNullTypeAdapterFactory() {
    }

    @Override
    public <T> TypeAdapter<T> create(final Gson gson, final TypeToken<T> typeToken) {
        final TypeAdapter<T> delegateAdapter = gson.getAdapter(typeToken);
        return new TypeAdapter<T>() {
            @Override
            public void write(final JsonWriter out, final T value)
                    throws IOException {
                delegateAdapter.write(out, value);
            }

            @Override
            public T read(final JsonReader in)
                    throws IOException {
                // If the next token is a boolean
                if ( in.peek() == JsonToken.BOOLEAN ) {
                    // and it's true
                    if ( in.nextBoolean() ) {
                        // then just report an unexpected `true` literal
                        throw new MalformedJsonException("Expected a null value indicator as `false`. " + in);
                    }
                    // and it's false, then we assume it's a null
                    return null;
                }
                // Otherwise read the whole value as a usual
                return delegateAdapter.read(in);
            }
        };
    }

}

Once you deserialize the JSON documents you provided in the question, a toString-ed mapping might produce something like this:

Envelope{genetics=Genetics{names=null, ucpc=null, link=null}}  
Envelope{genetics=Genetics{names=Blueberry x Haze, ucpc=W74AFGH22Z000000000000000 x 9XVU7WJQCD000000000000000, link=https://www.cannabisreports.com/api/v1.0/strains/9XVU7PTG2P000000000000000/genetics}}  


回答2:

Since you already know you will have a boolean or a String, you can get a JsonPrimitive directly.

A primitive value is either a String, a Java primitive, or a Java primitive wrapper type.

Then from the JsonPrimitive instance, you can check if it isBoolean.

Here is how to use it in a small snippet

public static String getURL(JsonObject json, String value){
    JsonPrimitive data = json.get(value).getAsJsonPrimitive();
    if(data.isBoolean()){
        return "Boolean : " + data.getAsBoolean();
    } else {
        return "String : " + data.getAsString();
    }
}

And here is a quick example to validate this logic

public static void main(String[] args) {
    String data = 
            "{"
            + "    \"element\" : {"
            + "        \"a\":false,"
            + "        \"b\":\"foobar\""
            + "    }"
            + "}";
    JsonObject json = new JsonParser().parse(data).getAsJsonObject();
    JsonObject element = json.get("element").getAsJsonObject();
    System.out.println(getURL(element, "a"));
    System.out.println(getURL(element, "b"));
}

Boolean : false
String : foobar


The json used :

{
    "element" :{
        "a":false,
        "b":"foobar"
    }
}