Generic JsonDeserializer

先日、Gsonデシリアライザ ラムダで。 - Oboe吹きプログラマの黙示録
や、
Gsonデシリアライザ、汎用化? - Oboe吹きプログラマの黙示録
を書きましたが、
再考して、以下に至りました。
JsonDeserializer実装生成で目標のインスタンスSupplier と JsonDeserializerの
deserialize で求めるオブジェクトを返す function を渡すようにすれば、
GsonBuilder registerTypeAdapterをもっと簡潔に書けます。
BiFunction も思い浮かんだのですが、JsonElement 解析実行するラムダの中で
更に、JsonDeserializationContext deserializeメソッドで他のアダプタのデシリアライザに処理させることを
書くために、BiFunction ではダメだと気づきました。
この理由で、以下、3つの引数で目標のインスタンスを取得する関数型インターフェースを定義します。

@FunctionalInterface
public interface JsonDeserializeFunction<S, T, C>{
   T apply(S s, T t, C c);
}

S = Stream<Entry<String, JsonElement>>
T = 対象
C = JsonDeserializationContext
return = T と同じ型

registerTypeAdapter に渡す JsonDeserializer実装、

import java.lang.reflect.Type;
import java.util.Map.Entry;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.stream.Stream;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonParseException;
/**
 * GenericDeserializer
 */
public class GenericDeserializer<T> implements JsonDeserializer<T>{
   private JsonDeserializeFunction<Stream<Entry<String, JsonElement>>,
                            T, JsonDeserializationContext> function;
   private Supplier<T> supplier;

   public GenericDeserializer(Supplier<T> supplier,
                        JsonDeserializeFunction<Stream<Entry<String, JsonElement>>,
                        T, JsonDeserializationContext> function){
      this.supplier = supplier;
      this.function = function;
   }
   @Override
   public T deserialize(JsonElement json, Type typeOfT,
                   JsonDeserializationContext context) throws JsonParseException{
      T t = supplier.get();
      if (!json.isJsonNull()){
         t = function.apply(json.getAsJsonObject().entrySet()
                        .stream()
                        .filter(Predicate.not(e->e.getValue().isJsonNull())),
                       t, context);
      }
      return t;
   }
}

使用例
JSON のサンプル

{
  foo: { name:"X", date: "2019/03/12" },
  alist: [ { name:"A", date: "2019/03/06" },
           { name:"B", date: "2019/03/07" },
           { name:"C", date: "2019/03/08" }
         ]
}

Json 読込みから生成されるクラス
Foo.class

import java.time.LocalDate;

public class Foo{
   public String name;
   public LocalDate date;
   public Foo(){}
   public void setName(String name){
      this.name = name;
   }
   public void setDate(LocalDate date){
      this.date = date;
   }
}

Fdata.class

import java.util.List;

public class Fdata{
   public Foo foo;
   public List<Foo> alist;
}

Gson 生成→ fromJson

import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.Optional;
import org.labo.devjson.GenericDeserializer;
import org.labo.fileio.FileTool;
import org.yipuran.gsonhelper.LocalDateAdapter;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.reflect.TypeToken;
Gson gson = new GsonBuilder().serializeNulls()
.registerTypeAdapter(new TypeToken<LocalDate>(){}.getType(),
                        LocalDateAdapter.of("yyyy/MM/dd"))
.registerTypeAdapter(new TypeToken<Foo>(){}.getType(),
 new GenericDeserializer<>(()->new Foo(), (s, t, c)->{
   s.forEach(e->{
      if (e.getKey().equals("name")) t.setName(e.getValue().getAsString());
      if (e.getKey().equals("date"))
         t.setDate(c.deserialize(e.getValue(), new TypeToken<LocalDate>(){}.getType()));
   });
   return t;
}))
.create();

Fdata data = gson.fromJson(str, new TypeToken<Fdata>(){}.getType());

System.out.println("data.foo.name = "
+ Optional.ofNullable(data.foo).map(e->e.name).orElse(null)
+ " date = " + Optional.ofNullable(data.foo.date).orElse(null)
);
if (data.alist != null){
   data.alist.stream().forEach(e->{
      System.out.println("alist  name=" + e.name
      + "  date = " + Optional.ofNullable(e.date).orElse(null));
   });
}


LocalDate は、LocalDateAdapter.of("yyyy/MM/dd")
で、デシリアライズするようにしてます。
https://github.com/yipuran/yipuran-gsonhelper/wiki#localdate-adapter


結果

data.foo.name = X date = 2019-03-12
alist  name=A  date = 2019-03-06
alist  name=B  date = 2019-03-07
alist  name=C  date = 2019-03-08

更にもっと簡潔にするなら、
JsonDeserializeFunction<S, T, C> ラムダで書く
Stream<Entry<String, JsonElement>> の forEach ではなく、collect を使って、

.registerTypeAdapter(new TypeToken<Foo>(){}.getType(), new GenericDeserializer<>(()->new Foo()
   , (s, t, c)->s.collect(()->t, (r, u)->{
      if (u.getKey().equals("name")) r.setName(u.getValue().getAsString());
      if (u.getKey().equals("date"))
          r.setDate(c.deserialize(u.getValue(), new TypeToken<LocalDate>(){}.getType()));
   }, (r, u)->{})))

つまり、collect 集計処理でこういうことです。

.registerTypeAdapter(new TypeToken<Foo>(){}.getType(), new GenericDeserializer<>(()->new Foo()
   , (s, t, c)->s.collect(()->t, (r, u)->{
      // TODO u.getKey() で求めるJSONキーに対して、u.getValue()で取得する JsonElement から
      // 値を読み取って、r に格納する。
      // 必要に応じで、JsonDeserializationContext である c の deserialize 結果を
      // r に格納する。
   }, (r, u)->{})))

GenericDeserializer コンストラクタの Supplier の結果を、collect の Supplier に引き継がせるのです。