Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Gson treat failed field parsing as null

Tags:

java

gson

Is there a way to configure Gson so that it treats any failed field parse as null instead of throwing a parse exception? Ideally we could catch and log the exception -- but we want the option to keep going with the program even if some fields (or subfields) do not parse as expected.

Example:

Malformed JSON:

{
   "dog": []
}

With classes:

class Farm {
  public Dog dog;
}

class Dog {
  public String name;
}

Gson gson = new Gson();
Farm oldMcdonald = gson.fromJson(json, Farm.class); // should not throw exception
assertNull(oldMcdonald.dog); // should pass
like image 491
Paul Avatar asked Nov 30 '25 13:11

Paul


1 Answers

In Gson, it can be implemented pretty easy. Despite the following solution, I guess, seems not to work in any case (for example, primitives), it can be enhanced if necessary.

final class JsonFailSafeTypeAdapterFactory
        implements TypeAdapterFactory {

    private static final TypeAdapterFactory instance = new JsonFailSafeTypeAdapterFactory();

    private JsonFailSafeTypeAdapterFactory() {
    }

    static TypeAdapterFactory get() {
        return instance;
    }

    @Override
    public <T> TypeAdapter<T> create(final Gson gson, final TypeToken<T> typeToken) {
        // We can support non-primitive types only
        if ( typeToken.getRawType().isPrimitive() ) {
            return null;
        }
        final TypeAdapter<T> delegateTypeAdapter = gson.getAdapter(typeToken);
        return new JsonFailSafeTypeAdapter<>(delegateTypeAdapter);
    }

    private static final class JsonFailSafeTypeAdapter<T>
            extends TypeAdapter<T> {

        private final TypeAdapter<T> delegateTypeAdapter;

        private JsonFailSafeTypeAdapter(final TypeAdapter<T> delegateTypeAdapter) {
            this.delegateTypeAdapter = delegateTypeAdapter;
        }

        @Override
        public void write(final JsonWriter out, final T value)
                throws IOException {
            delegateTypeAdapter.write(out, value);
        }

        @Override
        public T read(final JsonReader in)
                throws IOException {
            try {
                return delegateTypeAdapter.read(in);
            } catch ( final MalformedJsonException | RuntimeException ignored ) {
                // Once we get into unexpected JSON token, let's *always* consider a fallback to the default value
                // Well, the default is always `null` anyway, but we'll do more work
                return fallback(in);
            }
        }

        private static <T> T fallback(final JsonReader in)
                throws IOException {
            final JsonToken jsonToken = in.peek();
            switch ( jsonToken ) {
            case BEGIN_ARRAY:
            case BEGIN_OBJECT:
            case NAME:
            case STRING:
            case NUMBER:
            case BOOLEAN:
            case NULL:
                // Assume we're at the beginning of a complex JSON value or a JSON primitive
                in.skipValue();
                break;
            case END_ARRAY:
                // Not sure if we skipValue() can fast-forward this one
                in.endArray();
                break;
            case END_OBJECT:
                // The same
                in.endObject();
                break;
            case END_DOCUMENT:
                // do nothing
                break;
            default:
                throw new AssertionError(jsonToken);
            }
            // Just return null (at least at the moment)
            return null;
        }

    }

}

Now just register the above type factory to handle all types (except java.lang.Object if I'm not mistaken).

private static final Gson gson = new GsonBuilder()
        .registerTypeAdapterFactory(JsonFailSafeTypeAdapterFactory.get())
        .create();

public static void main(final String... args)
        throws IOException {
    try ( final JsonReader jsonReader = Resources.getPackageResourceJsonReader(Q50002961.class, "farm.json") ) {
        final Farm oldMcdonald = gson.fromJson(jsonReader, Farm.class);
        if ( oldMcdonald.dog != null ) {
            throw new AssertionError();
        }
        System.out.println(oldMcdonald);
    }
}

Example output:

q50002961.Farm@626b2d4a

Another option is also specifying target fields if there is no need to register the factory globally. For instance:

final class Farm {

    @JsonAdapter(JsonFailSafeTypeAdapterFactory.class)
    final Dog dog = null;

}
like image 78
Lyubomyr Shaydariv Avatar answered Dec 02 '25 05:12

Lyubomyr Shaydariv



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!