あなたのリクエストを達成するためのGsonのオプションはなく、あなたはそのような注釈を自分で処理する必要があります。理想的には、Gsonがvisit ReflectiveTypeAdapterFactory
を提供したり、おそらくExclusionStrategy
を拡張して、関連するフィールドと共にフィールド値にアクセスすることができれば素晴らしいでしょう。しかし、それらのどれも用意されていないが、それは次のいずれかのオプションを取ることが可能です:
- は
Map<String, Object>
インスタンス(構築する中間オブジェクトを必要とし、おそらく高価な)にご値オブジェクトを変換します。
@DefaultValue
-aware ReflectiveTypeAdapterFactory
(私はそれが最善の解決策だと思いますが、おそらくもっと一般化することができます)を再実装します。
- 一時的にシリアル化されたオブジェクトからフィールドのフィールドを取り除き、それらがシリアル化されると状態を元に戻します(安全でない可能性があり、おそらくパフォーマンスが低下する可能性があります)。
- 値をクローン化し、ヌルにしたがって値を取り除くので、元に戻すことを心配する必要もありません(高価かもしれません)。
#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 {ラベル、ALIGN =」 c '、size = '12'}
{"label" : "ラベル"}
LabelWidget {ラベル= 'ラベル'、ALIGN = 'C'、サイズ= '12' }
それとも、あなたのデータ転送アーキテクチャのも再検討し、設計かもしれない、そしておそらく(実際にはnull
とundefined
のようなものを区別することはできません)。
'transient'キーワードは機能しませんか? '一時的な文字列dontSerialiseMe = "デフォルト"'; –
注釈のないフィールドにデフォルト値を直接割り当てるだけでなく、JSONにシリアル化されているかどうか気にする必要はありませんか? JSONペイロードが大きすぎますか? –