3

I am using Gson to serialize and deserialize JSON. In my tests, I found that when I pass my Gson object an empty string, it returns null rather than throwing a JsonParseException.

How can I configure Gson so that it throws an error for empty strings?


Here is my deserializer:

public final class ProjectDeserializer implements JsonDeserializer<Project> {

    @Override
    public Project deserialize(final JsonElement jsonElement, final Type type, final JsonDeserializationContext context) throws JsonParseException {

        final JsonObject jsonObject = jsonElement.getAsJsonObject();

        final Identifier name = context.deserialize(jsonObject.get("name"), Identifier.class);

        Optional<String> license;
        if (jsonObject.has("license")) {
            license = Optional.of(jsonObject.get("license").getAsString());
        } else {
            license = Optional.empty();
        }

        DependencyGroup dependencies;
        if (jsonObject.has("dependencies")) {
            dependencies = context.deserialize(jsonObject.get("dependencies"), DependencyGroup.class);
        } else {
            dependencies = DependencyGroup.of();
        }

        return Project.of(name, license, dependencies);
    }
}

Here is my Gson factory:

public static Gson gson() {

    final GsonBuilder gsonBuilder = new GsonBuilder();

    // etc...

    gsonBuilder.registerTypeAdapter(Project.class, new ProjectSerializer());
    gsonBuilder.registerTypeAdapter(Project.class, new ProjectDeserializer());

    // etc...

    return gsonBuilder.create();
}

1 Answer 1

3

You cannot make it like that out of box. Currently com.google.gson.Gson has the following code protecting code:

public <T> T fromJson(JsonReader reader, Type typeOfT) throws JsonIOException, JsonSyntaxException {
    boolean isEmpty = true;
    ...
    try {
      reader.peek();
      ...
    } catch (EOFException e) {
      /*
       * For compatibility with JSON 1.5 and earlier, we return null for empty
       * documents instead of throwing.
       */
      if (isEmpty) {
        return null;
      }
      throw new JsonSyntaxException(e);

Passing an empty or a blank string to the method makes the reader.peek(); line to throw an EOFException, and the isEmpty flag is set to true at that moment. And this is why your deserializer has no any effect on it: it's not even invoked. This behavior was introduced in commit 25c6ae177b1ca56db7f3c29eb574bdd032a06165 back in 2011 with the following commit message excerpt:

For backwards compatibility, return null for the empty string.

What you can do is either making sure that you check deserialized objects for null, or (if centralized deserialization is possible for you [at least it should be]) make sure that Gson.fromJson(JsonReader,Type) is used for deserialization purposes: this is where you can inject a custom JsonReader wrapping EOFExceptions into JsonSyntaxExceptions that are not currently caught in that method (at least as of Gson 2.8.0). For example:

public class EmptyStringFailFastJsonReader
        extends JsonReader {

    public EmptyStringFailFastJsonReader(final Reader in) {
        super(in);
    }

    @Override
    public JsonToken peek()
            throws IOException {
        try {
            return super.peek();
        } catch ( final EOFException ex ) {
            throw new JsonSyntaxException(ex);
        }
    }

}

This would break the protecting code in Gson. A few examples below. The code that does not fail:

gson.fromJson("", fooType);
gson.fromJson(new StringReader(""), fooType);

But the following one does:

gson.fromJson(new EmptyStringFailFastJsonReader(new StringReader("")), fooType);

With the following exception message:

com.google.gson.JsonSyntaxException: java.io.EOFException: End of input at line 1 column 1 path $

Sign up to request clarification or add additional context in comments.

1 Comment

Thanks for the detailed answer. I think the best solution for my project is to have static methods for each type I might deserialize and do the check there.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.