フラットに属性が並んだオブジェクトから、階層のあるJSON への変換(2)

フラットに属性が並んだオブジェクトから、階層のあるJSON への変換(1)の続き。

属性フィールドに、グルーピング名をアノテーションで付与することで、この処理を汎用的に行う
リアライザを考えた。

public class Sample{
   public String title;
   @Grouping("Unit")
   public String name;
   @Grouping("Unit")
   public int value;
}

これで、 name と value を、以下のJSONのように、グループする。

{
    "title": "あいう",
    "Unit": {
      "name": "A",
      "value": 121
    }
}

アノテーション

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
 * Grouping
 */
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.FIELD})
public @interface Grouping {
     String value();
     boolean nullSafe() default true;
}

Grouping アノテーションが付いたフィールドを処理するシリアライザ

import java.lang.reflect.Field;
import java.lang.reflect.Type;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;
import com.google.gson.annotations.SerializedName;
/**
 * GroupingSerializer
 */
public class GroupingSerializer<T> implements JsonSerializer<T>{

   @Override
   public JsonElement serialize(T src, Type typeOfSrc, JsonSerializationContext context){
      JsonObject jo = new JsonObject();
      Field[] fields = src.getClass().getDeclaredFields();
      Map<String, JsonObject> jmap = new HashMap<>();
      for(Field f:fields) {
         try{
            f.setAccessible(true);
            Object obj = f.get(src);
            JsonObject addjo;
            Grouping g = f.getAnnotation(Grouping.class);
            if (g != null) {
               String jkey = g.value();
               addjo = jmap.containsKey(jkey) ? jmap.get(jkey) : new JsonObject();
               jmap.put(jkey, addjo);
            }else{
               addjo = jo;
            }
            if (obj instanceof String) {
               addjo.addProperty(Optional.ofNullable(f.getAnnotation(SerializedName.class)).map(e->e.value()).orElse(f.getName())
                     , Optional.ofNullable(obj).map(e->e.toString()).orElse(null));
            }else {
               addjo.add(Optional.ofNullable(f.getAnnotation(SerializedName.class)).map(e->e.value()).orElse(f.getName())
                     , context.serialize(obj, f.getType()));
            }
         }catch(IllegalAccessException e){
            throw new RuntimeException(e);
         }
      }
      jmap.entrySet().stream().forEach(e->jo.add(e.getKey(), e.getValue()));
      return jo;
   }

}

@SerializedName も働くようにしてある。