简体   繁体   English

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

[英]GSON how to skip serialization of specific default values?

How can I get JSON to skip serialization of fields with specific default values? 如何获取JSON以跳过具有特定默认值的字段的序列化? I can for example annotate the fields with a custom annotation for a TypeAdapter to parse, however I struggle finding out how to write such TypeAdapter without completely reinventing the wheel (ie I could skip the write method of ReflectiveTypeAdapterFactory and write my own with reflection). 例如,我可以使用用于TypeAdapter解析的自定义注释来为字段添加注释,但是我很难找出如何编写这种TypeAdapter而不完全重新发明轮子的方法(即,我可以跳过ReflectiveTypeAdapterFactorywrite方法,并使用反射来编写自己的方法)。

Background: I'm sending a GUI over Json, and I want to expand eg a panel widget with every possible property (background, border, etc.) but not send all of those as null values because most panels use default values anyway. 背景:我正在通过Json发送GUI,并且我想扩展例如具有所有可能属性(背景,边框等)的面板小部件,但不要将所有这些都作为空值发送,因为大多数面板仍然使用默认值。

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

Objects: 对象:

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

Wanted results: 想要的结果:

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

Not sure how @DefaultValue integrates with GSON but one of the solution that works is to actually nullify fields in case of default values during construction time eg: 不确定@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;
}

In such case your getters will return correct values and GSON will not serialize null values. 在这种情况下,您的获取器将返回正确的值,而GSON不会序列化null值。

There are no options in Gson like that to accomplish your request and you still have to process such an annotation yourself. Gson中没有其他选项可以完成您的请求,您仍然必须自己处理此类注释。 Ideally, it would be great if Gson could provide visiting ReflectiveTypeAdapterFactory , or probably enhance ExclusionStrategy in order to access fields values along with associated fields. 理想情况下,如果Gson可以提供访问ReflectiveTypeAdapterFactory ,或者可能增强ExclusionStrategy以便与关联字段一起访问字段值,那就太好了。 However, none of those are available, but it's possible to take one of the following options: 但是,这些都不可用,但是可以采用以下选项之一:

  • convert your value objects to Map<String, Object> instances (requires intermediate objects to be constructed; probably expensive); 将您的值对象转换为Map<String, Object>实例(需要构造中间对象;可能很昂贵);
  • re-implement a @DefaultValue -aware ReflectiveTypeAdapterFactory (I guess it's the best solution, but probably it could be even more generalized); 重新实现一个@DefaultValue ReflectiveTypeAdapterFactory (我想这是最好的解决方案,但可能会更加概括);
  • temporarily strip the @DefaultValue -annotated fields from serialized objects and revert their state back once they are serialized (potentially unsafe and probably a performance hit); 暂时从序列化的对象中@DefaultValue字段,并在序列化@DefaultValue其状态恢复为原来的状态(可能不安全,并且可能会降低性能);
    • clone values and stripping the values according to nulls so no worrying about reverting back (may be expensive too). 克隆值并根据空值剥离值,因此不必担心还原(可能也很昂贵)。

Option #3 can be implemented as follows: 选项#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);
            }
        }

    }

}

So: 所以:

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'}
{"label":"label"} { “标签”: “标签”}
LabelWidget{label='label', align='c', size='12'} LabelWidget {label ='label',align ='c',size = '12'}

Or you might also re-consider the design of your data transfer architecture, and probably proceed with just nulls (that however does not let distinguish between "really" null and something like undefined ). 或者,您也可以重新考虑数据传输体系结构的设计,并可能只使用null(但是不能区分“真正的” nullundefined东西)。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM