首页 > 解决方案 > Gson 将失败的字段解析视为 null

问题描述

有没有办法配置 Gson 以便它将任何失败的字段解析视为 null 而不是抛出解析异常?理想情况下,我们可以捕获并记录异常——但即使某些字段(或子字段)未按预期解析,我们也希望该选项继续执行程序。

例子:

格式错误的 JSON:

{
   "dog": []
}

与类:

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

标签: javagson

解决方案


在 Gson 中,它可以很容易地实现。尽管我猜以下解决方案似乎在任何情况下都不起作用(例如,原语),但如有必要,可以对其进行增强。

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;
        }

    }

}

现在只需注册上述类型工厂来处理所有类型(如果我没记错的话,java.lang.Object 除外)。

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);
    }
}

示例输出:

q50002961.Farm@626b2d4a

如果不需要全局注册工厂,另一个选项也是指定目标字段。例如:

final class Farm {

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

}

推荐阅读