先日、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 に引き継がせるのです。