如何让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%"}
答案 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'}
或者您也可能会重新考虑数据传输体系结构的设计,并且可能只使用空值(但不能区分“真正”null
和undefined
之类的内容。