GSON如何跳过特定默认值的序列化?

时间:2017-02-06 09:19:51

标签: java json gson

如何让JSON跳过具有特定默认值的字段序列化?我可以使用自定义注释来注释字段,以便TypeAdapter进行解析,但是我很难找到如何在不完全重新发明轮子的情况下编写这样的TypeAdapter(即我可以跳过write ReflectiveTypeAdapterFactory的方法,用反射写我自己的方法。

背景:我正在通过Json发送GUI,我想扩展,例如一个面板小部件,包含所有可能的属性(背景,边框等),但不会将所有这些属性作为空值发送,因为大多数面板仍然使用默认值。

POJO:

public class LabelWidget {
    private final String label;
    @DefaultValue("c")  private final String align;
    @DefaultValue("12") private final String size;

    public LabelWidget(String label, String align, String size) {
        ...
    }

    public String getLabel() {return label;}
    public String getAlign() {return align==null||align.isEmpty() ? "c" : align;}
    public String getSize()  {return size==null||size.isEmpty() ? "12" : size;}
}

物件:

a = new LabelWidget("foo", "c", "12");
b = new LabelWidget("bar", "l", "50%");

通缉结果:

{label:"foo"}
{label:"bar", align:"l", size:"50%"}

2 个答案:

答案 0 :(得分:0)

不确定@DefaultValue如何与GSON集成,但有效的解决方案之一是在构造时间内默认情况下实际取消字段,例如:

public LabelWidget(String label, String align, String size) {
    this.label = label;
    this.align = StringUtils.isBlank(align) || "c".equals(align) ? null : align;
    this.size = StringUtils.isBlank(size) || "12".equals(size) ? null : size;
}

在这种情况下,您的getter将返回正确的值,GSON将不会序列化null值。

答案 1 :(得分:0)

Gson中没有任何选项来完成您的请求,您仍然需要自己处理这样的注释。理想情况下,如果Gson可以提供访问ReflectiveTypeAdapterFactory,或者可能增强ExclusionStrategy以便访问字段值以及相关字段,那就太棒了。但是,这些都不可用,但可以采用以下选项之一:

  • 将您的值对象转换为Map<String, Object>个实例(需要构建中间对象;可能很昂贵);
  • 重新实现一个@DefaultValue - 意识到ReflectiveTypeAdapterFactory(我猜这是最好的解决方案,但可能会更加普遍化);
  • 暂时从序列化对象中删除@DefaultValue - 带注释的字段,并在序列化后将其状态恢复(可能不安全,可能会影响性能);
    • 克隆值并根据空值剥离值,因此不必担心还原(可能也很昂贵)。

选项#3可以如下实现:

@Target(FIELD)
@Retention(RUNTIME)
@interface DefaultValue {

    String value() default "";

}
final class DefaultValueTypeAdapterFactory
        implements TypeAdapterFactory {

    private static final TypeAdapterFactory defaultValueTypeAdapterFactory = new DefaultValueTypeAdapterFactory();

    private DefaultValueTypeAdapterFactory() {
    }

    static TypeAdapterFactory getDefaultValueTypeAdapterFactory() {
        return defaultValueTypeAdapterFactory;
    }

    @Override
    public <T> TypeAdapter<T> create(final Gson gson, final TypeToken<T> typeToken) {
        if ( DefaultValueTypeAdapter.hasDefaults(typeToken.getType()) ) {
            return new DefaultValueTypeAdapter<>(gson.getDelegateAdapter(this, typeToken));
        }
        return null;
    }

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

        private final TypeAdapter<T> delegateAdapter;

        private DefaultValueTypeAdapter(final TypeAdapter<T> delegateAdapter) {
            this.delegateAdapter = delegateAdapter;
        }

        @Override
        public void write(final JsonWriter out, final T value)
                throws IOException {
            final Map<Field, Object> defaults = getDefaults(value);
            try {
                resetFields(value, defaults.keySet());
                delegateAdapter.write(out, value);
            } finally {
                setFields(value, defaults);
            }
        }

        @Override
        public T read(final JsonReader in)
                throws IOException {
            final T value = delegateAdapter.read(in);
            trySetAnnotationDefaults(value);
            return value;
        }

        private static boolean hasDefaults(final Type type) {
            if ( !(type instanceof Class) ) {
                return false;
            }
            final Class<?> c = (Class<?>) type;
            return Stream.of(c.getDeclaredFields())
                    .flatMap(f -> Stream.of(f.getAnnotationsByType(DefaultValue.class)))
                    .findAny()
                    .isPresent();
        }

        private static Map<Field, Object> getDefaults(final Object o) {
            if ( o == null ) {
                return emptyMap();
            }
            final Class<?> c = o.getClass();
            final Map<Field, Object> map = Stream.of(c.getDeclaredFields())
                    .filter(f -> f.isAnnotationPresent(DefaultValue.class))
                    .filter(f -> !f.getType().isPrimitive()) // primitive fields cause ambiguities
                    .peek(f -> f.setAccessible(true))
                    .filter(f -> {
                        final String defaultValue = f.getAnnotation(DefaultValue.class).value();
                        final String comparedValue = ofNullable(getUnchecked(o, f)).map(Object::toString).orElse(null);
                        return defaultValue.equals(comparedValue);
                    })
                    .collect(toMap(identity(), f -> getUnchecked(o, f)));
            return unmodifiableMap(map);
        }

        private static void trySetAnnotationDefaults(final Object o) {
            if ( o == null ) {
                return;
            }
            final Class<?> c = o.getClass();
            Stream.of(c.getDeclaredFields())
                    .filter(f -> f.isAnnotationPresent(DefaultValue.class))
                    .forEach(f -> {
                        f.setAccessible(true);
                        if ( getUnchecked(o, f) == null ) {
                            final String annotationValue = f.getAnnotation(DefaultValue.class).value();
                            setOrDefaultUnchecked(o, f, parseDefaultValue(f.getType(), annotationValue));
                        }
                    });
        }

        private static Object parseDefaultValue(final Class<?> type, final String rawValue) {
            if ( type == String.class ) {
                return rawValue;
            }
            if ( type == Boolean.class ) {
                return Boolean.valueOf(rawValue);
            }
            if ( type == Byte.class ) {
                return Byte.valueOf(rawValue);
            }
            if ( type == Short.class ) {
                return Short.valueOf(rawValue);
            }
            if ( type == Integer.class ) {
                return Integer.valueOf(rawValue);
            }
            if ( type == Long.class ) {
                return Long.valueOf(rawValue);
            }
            if ( type == Float.class ) {
                return Float.valueOf(rawValue);
            }
            if ( type == Double.class ) {
                return Double.valueOf(rawValue);
            }
            if ( type == Character.class ) {
                final int length = rawValue.length();
                if ( length != 1 ) {
                    throw new IllegalArgumentException("Illegal raw value length: " + length + " for " + rawValue);
                }
                return rawValue.charAt(0);
            }
            throw new AssertionError(type);
        }

        private static void resetFields(final Object o, final Iterable<Field> fields) {
            fields.forEach(f -> setOrDefaultUnchecked(o, f, null));
        }

        private static void setFields(final Object o, final Map<Field, Object> defaults) {
            if ( o == null ) {
                return;
            }
            defaults.entrySet().forEach(e -> setOrDefaultUnchecked(o, e.getKey(), e.getValue()));
        }

        private static Object getUnchecked(final Object o, final Field field) {
            try {
                return field.get(o);
            } catch ( final IllegalAccessException ex ) {
                throw new RuntimeException(ex);
            }
        }

        private static void setOrDefaultUnchecked(final Object o, final Field field, final Object value) {
            try {
                field.set(o, value);
            } catch ( final IllegalAccessException ex ) {
                throw new RuntimeException(ex);
            }
        }

    }

}

所以:

final Gson gson = new GsonBuilder()
        .registerTypeAdapterFactory(getDefaultValueTypeAdapterFactory())
        .create();
final LabelWidget before = new LabelWidget("label", "c", "12");
out.println(before);
final String json = gson.toJson(before);
out.println(json);
final LabelWidget after = gson.fromJson(json, LabelWidget.class);
out.println(after);
  

LabelWidget {label ='label',align ='c',size ='12'}
  { “标签”: “标签”}
  LabelWidget {label ='label',align ='c',size ='12'}

或者您也可能会重新考虑数据传输体系结构的设计,并且可能只使用空值(但不能区分“真正”nullundefined之类的内容。