Gson: 在反序列化时怎样可以忽略空字段?

Created on 27 Aug 2018  ·  1Comment  ·  Source: google/gson

public class Data {
@NonNull
List strings = new ArrayList<>();
}

String json = "{\"strings\":null}";
Data data = new Gson().fromJson(json, new TypeToken(){}.getType());
System.out.println(data.strings);

返回结果是:null,我期望返回:[]。

Most helpful comment

Why do you think that Gson should keep the initialization values if you set strings to null explicitly?

Gson works as expected. Note that field initializers _do not exist per se_ and they are simply copied to class constructors by javac. Moreover, you cannot access field initializers in runtime just because of that. Gson only uses the default constructor only if it exists.

You can work around it though:

final class NonnullTypeAdapterFactory
        implements TypeAdapterFactory {

    private final Function<? super Field, ? extends Supplier<?>> getDefaultFieldValue;

    private NonnullTypeAdapterFactory(final Function<? super Field, ? extends Supplier<?>> getDefaultFieldValue) {
        this.getDefaultFieldValue = getDefaultFieldValue;
    }

    static TypeAdapterFactory get(final Function<? super Field, ? extends Supplier<?>> getDefaultFieldValue) {
        return new NonnullTypeAdapterFactory(getDefaultFieldValue);
    }

    @Override
    @Nullable
    public <T> TypeAdapter<T> create(final Gson gson, final TypeToken<T> typeToken) {
        @SuppressWarnings("unchecked")
        final TypeAdapter<T> delegateTypeAdapter = (TypeAdapter<T>) gson.getDelegateAdapter((TypeAdapterFactory) this, (TypeToken<?>) typeToken);
        if ( !(delegateTypeAdapter instanceof ReflectiveTypeAdapterFactory.Adapter) ) {
            return null;
        }
        return NonnullTypeAdapter.of(delegateTypeAdapter, typeToken, getDefaultFieldValue);
    }

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

        private final TypeAdapter<T> typeAdapter;
        private final Map<Field, Supplier<?>> nonnullFields;

        private NonnullTypeAdapter(final TypeAdapter<T> typeAdapter, final Map<Field, Supplier<?>> nonnullFields) {
            this.typeAdapter = typeAdapter;
            this.nonnullFields = nonnullFields;
        }

        public static <T> NonnullTypeAdapter<T> of(final TypeAdapter<T> typeAdapter, final TypeToken<T> typeToken,
                final Function<? super Field, ? extends Supplier<?>> getDefaultFieldValue) {
            final List<Class<?>> hierarchyClasses = getHierarchyClasses(typeToken.getRawType());
            final Map<Field, Supplier<?>> nonnullFields = hierarchyClasses.stream()
                    .flatMap(clazz -> Stream.of(clazz.getDeclaredFields()))
                    .filter(field -> field.getAnnotation(Nonnull.class) != null)
                    .peek(field -> field.setAccessible(true))
                    .collect(Collectors.collectingAndThen(Collectors.toMap(Function.identity(), getDefaultFieldValue), Collections::unmodifiableMap));
            return new NonnullTypeAdapter<>(typeAdapter, nonnullFields);
        }

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

        @Override
        public T read(final JsonReader in)
                throws IOException {
            try {
                final T object = typeAdapter.read(in);
                for ( final Map.Entry<Field, Supplier<?>> entry : nonnullFields.entrySet() ) {
                    final Field field = entry.getKey();
                    final Supplier<?> defaultValue = entry.getValue();
                    field.set(object, defaultValue.get());
                }
                return object;
            } catch ( final IllegalArgumentException | IllegalAccessException ex ) {
                throw new RuntimeException(ex);
            }
        }

    }

    private static List<Class<?>> getHierarchyClasses(final Class<?> clazz) {
        final List<Class<?>> classes = new ArrayList<>();
        for ( Class<?> c = clazz; c != null && c != Object.class; c = c.getSuperclass() ) {
            classes.add(c);
        }
        return Collections.unmodifiableList(classes);
    }

}
private static final Gson gson = new GsonBuilder()
        .registerTypeAdapterFactory(NonnullTypeAdapterFactory.get(field -> {
            final Class<?> type = field.getType();
            if ( List.class.isAssignableFrom(type) ) {
                return ArrayList::new;
            }
            return () -> null;
        }))
        .create();

public static void main(final String... args) {
    final String json = "{\"strings\":null}";
    final Data data = gson.fromJson(json, Data.class);
    System.out.println(data.strings);
}

will produce:

[]

Again, not a bug.

>All comments

Why do you think that Gson should keep the initialization values if you set strings to null explicitly?

Gson works as expected. Note that field initializers _do not exist per se_ and they are simply copied to class constructors by javac. Moreover, you cannot access field initializers in runtime just because of that. Gson only uses the default constructor only if it exists.

You can work around it though:

final class NonnullTypeAdapterFactory
        implements TypeAdapterFactory {

    private final Function<? super Field, ? extends Supplier<?>> getDefaultFieldValue;

    private NonnullTypeAdapterFactory(final Function<? super Field, ? extends Supplier<?>> getDefaultFieldValue) {
        this.getDefaultFieldValue = getDefaultFieldValue;
    }

    static TypeAdapterFactory get(final Function<? super Field, ? extends Supplier<?>> getDefaultFieldValue) {
        return new NonnullTypeAdapterFactory(getDefaultFieldValue);
    }

    @Override
    @Nullable
    public <T> TypeAdapter<T> create(final Gson gson, final TypeToken<T> typeToken) {
        @SuppressWarnings("unchecked")
        final TypeAdapter<T> delegateTypeAdapter = (TypeAdapter<T>) gson.getDelegateAdapter((TypeAdapterFactory) this, (TypeToken<?>) typeToken);
        if ( !(delegateTypeAdapter instanceof ReflectiveTypeAdapterFactory.Adapter) ) {
            return null;
        }
        return NonnullTypeAdapter.of(delegateTypeAdapter, typeToken, getDefaultFieldValue);
    }

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

        private final TypeAdapter<T> typeAdapter;
        private final Map<Field, Supplier<?>> nonnullFields;

        private NonnullTypeAdapter(final TypeAdapter<T> typeAdapter, final Map<Field, Supplier<?>> nonnullFields) {
            this.typeAdapter = typeAdapter;
            this.nonnullFields = nonnullFields;
        }

        public static <T> NonnullTypeAdapter<T> of(final TypeAdapter<T> typeAdapter, final TypeToken<T> typeToken,
                final Function<? super Field, ? extends Supplier<?>> getDefaultFieldValue) {
            final List<Class<?>> hierarchyClasses = getHierarchyClasses(typeToken.getRawType());
            final Map<Field, Supplier<?>> nonnullFields = hierarchyClasses.stream()
                    .flatMap(clazz -> Stream.of(clazz.getDeclaredFields()))
                    .filter(field -> field.getAnnotation(Nonnull.class) != null)
                    .peek(field -> field.setAccessible(true))
                    .collect(Collectors.collectingAndThen(Collectors.toMap(Function.identity(), getDefaultFieldValue), Collections::unmodifiableMap));
            return new NonnullTypeAdapter<>(typeAdapter, nonnullFields);
        }

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

        @Override
        public T read(final JsonReader in)
                throws IOException {
            try {
                final T object = typeAdapter.read(in);
                for ( final Map.Entry<Field, Supplier<?>> entry : nonnullFields.entrySet() ) {
                    final Field field = entry.getKey();
                    final Supplier<?> defaultValue = entry.getValue();
                    field.set(object, defaultValue.get());
                }
                return object;
            } catch ( final IllegalArgumentException | IllegalAccessException ex ) {
                throw new RuntimeException(ex);
            }
        }

    }

    private static List<Class<?>> getHierarchyClasses(final Class<?> clazz) {
        final List<Class<?>> classes = new ArrayList<>();
        for ( Class<?> c = clazz; c != null && c != Object.class; c = c.getSuperclass() ) {
            classes.add(c);
        }
        return Collections.unmodifiableList(classes);
    }

}
private static final Gson gson = new GsonBuilder()
        .registerTypeAdapterFactory(NonnullTypeAdapterFactory.get(field -> {
            final Class<?> type = field.getType();
            if ( List.class.isAssignableFrom(type) ) {
                return ArrayList::new;
            }
            return () -> null;
        }))
        .create();

public static void main(final String... args) {
    final String json = "{\"strings\":null}";
    final Data data = gson.fromJson(json, Data.class);
    System.out.println(data.strings);
}

will produce:

[]

Again, not a bug.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

GoogleCodeExporter picture GoogleCodeExporter  ·  32Comments

LucianWang picture LucianWang  ·  42Comments

GoogleCodeExporter picture GoogleCodeExporter  ·  17Comments

GoogleCodeExporter picture GoogleCodeExporter  ·  20Comments

RobMans426 picture RobMans426  ·  20Comments