2017-02-06 20 views
0

のフィールドのシリアル化をスキップするにはどうすればJSONを得ることができますか?デフォルト値?たとえば、TypeAdapterを解析するためのカスタム注釈でフィールドに注釈を付けることはできますが、TypeAdapterの書き込み方法を見つけ出すのは難しいです(つまり、メソッドをスキップして、自分自身を反射で書き込むことができます)。GSON特定のデフォルト値のシリアル化をスキップする方法は?

背景:私はJsonを介してGUIを送信しています。すべての可能なプロパティ(背景、境界線など)を持つパネルウィジェットですが、ほとんどのパネルでデフォルト値が使用されるため、それらのすべてをnull値として送信しません。

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

オブジェクト:

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

'transient'キーワードは機能しませんか? '一時的な文字列dontSerialiseMe = "デフォルト"'; –

+0

注釈のないフィールドにデフォルト値を直接割り当てるだけでなく、JSONにシリアル化されているかどうか気にする必要はありませんか? JSONペイロードが大きすぎますか? –

答えて

0

ない@defaultValueがGSONと統合が、機能するソリューションの一つは、実際にどのように確認してください。

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

結果を募集工事時間中のデフォルト値の場合はフィールドを無効にします。例:

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

このような場合、ゲッターは正しい値を返し、GSONはnullの値をシリアル化しません。

+0

他の(必須)フィールドが他の(GUI以外の)メッセージでシリアライズする必要があるため、私の 'GsonBuilder'に' .serializeNulls() 'が既にあります。値のnullを持つフィールドをシリアル化またはスキップする必要があることを示すアノテーションや何かがありますか?そのような場合には –

+0

になります:)あなたはいくつかの値を除外したい場合は一時的に使うことができますが、常に除外されるか、あるいはヌルを直列化してはいけません - なぜ値が直列化されるかどうかは気になりますか? – pbielicki

0

あなたのリクエストを達成するための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' }

それとも、あなたのデータ転送アーキテクチャのも再検討し、設計かもしれない、そしておそらく(実際にはnullundefinedのようなものを区別することはできません)。

+0

私はこれを最初に見ていませんでしたが、あなたのコードを試した後、pbielickiの答えの複雑なバージョンのようです(ヌルに値を設定し、GSONオブジェクトに依存してヌルをスキップします)。 –

+0

@MarkJeronimus違いは、シリアライゼーション・スコープからこれらのオブジェクトを操作するときには、nullを指定する必要があります。上記のコードでは、NULL可能性はシリアル化するためにのみ使用され、特別な「デフォルト」メソッドを提供するクラスは必要ありません。 '@ DefaultValue'は十分です。 –

+0

確かに、開発者にとってより使いやすくなっています。 Btwでは、カスタムTypeAdapterのwriteメソッドがそれをオーバーライドしない限り、null値をデフォルトでスキップすることは可能ですか? –

関連する問題