Google gson でリストを復元する時の fromJson の書き方をよく忘れるのでメモ
Gson gson = new GsonBuilder().serializeNulls().create(); List<Foo> list = gson.fromJson(string, new TypeToken<Collection<Foo>>(){}.getType());
Google gson でリストを復元する時の fromJson の書き方をよく忘れるのでメモ
Gson gson = new GsonBuilder().serializeNulls().create(); List<Foo> list = gson.fromJson(string, new TypeToken<Collection<Foo>>(){}.getType());
Wicket の モーダルウィンドウを閉じる時に、ページ遷移 setResponsePage
を何も対処せずに実行すると、、
Reloading this page will cause modal window to disappear
が発生してしまう。
これを回避するには、モーダルウィンドウを設置するページで初期処理として
以下の JavaScript を実行しておくことだ。
Wicket.Window.unloadConfirmation = false;
先日書いたGSON使用時の String型のNULLパターン { "aaa": "null" } でなくて、
{ "aaa": "" } にするための TypeAdapterFactory を書き直しました。
import java.io.IOException; import com.google.gson.Gson; import com.google.gson.TypeAdapter; import com.google.gson.TypeAdapterFactory; import com.google.gson.reflect.TypeToken; import com.google.gson.stream.JsonReader; import com.google.gson.stream.JsonToken; import com.google.gson.stream.JsonWriter; /** * NullStringToEmptyAdapterFactory */ public class NullStringToEmptyAdapterFactory implements TypeAdapterFactory{ @Override public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) { Class<T> rawType = (Class<T>)type.getRawType(); if (rawType != String.class){ return null; } return (TypeAdapter<T>) new TypeAdapter<String>(){ @Override public String read(JsonReader reader) throws IOException{ if (reader.peek()==JsonToken.NULL){ reader.nextNull(); return ""; } return reader.nextString(); } @Override public void write(JsonWriter writer, String value) throws IOException{ if (value==null){ writer.value(""); return; } writer.value(value); } }; } }
Integer型も、対応するなら、ちょっと長くなりますが、、、
import java.io.IOException;
import com.google.gson.Gson;
import com.google.gson.TypeAdapter;
import com.google.gson.TypeAdapterFactory; import com.google.gson.reflect.TypeToken; import com.google.gson.stream.JsonReader; import com.google.gson.stream.JsonToken; import com.google.gson.stream.JsonWriter; /** * NullStringIntegerToEmptyAdapterFactory */ public class NullStringIntegerToEmptyAdapterFactory implements TypeAdapterFactory{ @Override public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) { Class<T> rawType = (Class<T>)type.getRawType(); if (rawType != String.class && rawType != Integer.class){ return null; } if (rawType==Integer.class){ return (TypeAdapter<T>) new TypeAdapter<Integer>(){ @Override public Integer read(JsonReader reader) throws IOException { if (reader.peek()==JsonToken.NULL){ reader.nextNull(); return null; } String s = reader.nextString(); return s==null || "".equals(s) ? null : new Integer(s); } @Override public void write(JsonWriter writer, Integer value) throws IOException { if (value==null){ writer.value(""); return; } writer.value(value.toString()); } }; } return (TypeAdapter<T>) new TypeAdapter<String>(){ @Override public String read(JsonReader reader) throws IOException{ if (reader.peek()==JsonToken.NULL){ reader.nextNull(); return ""; } return reader.nextString(); } @Override public void write(JsonWriter writer, String value) throws IOException{ if (value==null){ writer.value(""); return; } writer.value(value); } }; } }
google GSON でJSONを生成する場合、そのままだと、nullオブジェクトは無視(出力されない)なので、GsonBuilder の serializeNulls() を呼び出して、 { "a": "null" } のように出す。
しかし、String オブジェクトの結果として、{ "a": "" } が欲しい場合がある。
JsonSerializer<String> を実装した adapter を書いてみたが、うまくいかない。
古い情報だが、
http://stackoverflow.com/questions/9483348/gson-treat-null-as-empty-string
にヒントがあった。当時のバージョンよりバージョンUPが期待されていたが、2.6.2でも一向に解決されてない。stackoverflow に書いてあった TypeAdapterFactoryの方法が最も有効で、stackoverflowに書いてあったものを修正すると思いどおりになる。
import java.io.IOException; import com.google.gson.TypeAdapter; import com.google.gson.stream.JsonReader; import com.google.gson.stream.JsonToken; import com.google.gson.stream.JsonWriter; /** * StringAdapter */ public class StringAdapter extends TypeAdapter{ @Override public String read(JsonReader reader) throws IOException{ if (reader.peek() == JsonToken.NULL){ reader.nextNull(); return ""; } return reader.nextString(); } @Override public void write(JsonWriter writer, String value) throws IOException { if (value == null) { writer.value(""); return; } writer.value(value); } } public class NullStringToEmptyAdapterFactory implements TypeAdapterFactory{ public TypeAdapter create(Gson gson, TypeToken type){ Class rawType = (Class ) type.getRawType(); if (rawType != String.class){ return null; } return (TypeAdapter ) new StringAdapter(); } }
この TypeAdapterFactory の create で無名クラスで返すように書きたかったのだが、
うまく書けなかった。
Gson gson = new GsonBuilder()
.registerTypeAdapterFactory(new NullStringToEmptyAdapterFactory())
.create();
でも、Google group の投稿には、gsonで作成済の json 文字列に対して、replaceAll("nill", "\"\"") を実行すればいいじゃないのように、書いてある。
いや、確かにそうなんだけど。。。。
それなら、replaceAll(":\"null\"", "\"\"") って書くけど。。。replaceAll がもし負荷が上がるなら、TypeAdpter の方で処理するのが正解なのか。。。。
Wicket の AjaxButton onSubmit の実行前に処理したいJavaScript がある場合、
わざわざ jQuery で $( ボタンセレクタ ) .click( function(){ ...} ); のソースを用意して
JavaScriptHeaderItem.forReference で読み込ませる方法にするか、
生成したAjaxButton に、更に、 AjaxEventBehavior("click") の onEvent で、
AjaxRequestTarget # prependJavaScript で書くか。。
が通常、思いつく方法だが、
AjaxButton の中の getOnClickScript メソッドをオーバーライドして、
クリック時に実行したい JavaScript を渡して JavaScript 処理後にonSubmitの処理が
走る方法もある。
@Override
protected String getOnClickScript(){
return "$('#name').val('" + foo + "')";
}
Java7をやめてJava8で開発するようになり、java.util.Dateを使わなくなり、
Google gson で LocalDate / LocalDateTime をシリアライズ・デシリアライズするのに
簡単に書ける方法を探すのに苦労したのでその過程と結果です。
GsonBuilder の registerTypeAdapter で LocalDateのシリアライズ&デシリアライズ、
LocalDateTimeのシリアライズ&デシリアライズと、registerTypeAdapter の呼び出しを4回も書かなくてはならない。
new GsonBuilder().registerTypeAdapter(LocalDate.class, new JsonSerializer<LocalDate>(){
@Override
public JsonElement serialize(LocalDate date, Type type, JsonSerializationContext context){
return new JsonPrimitive(date.format(DateTimeFormatter.ofPattern("yyyy/MM/dd")));
}
}). /* このようなものを、毎回4つ書くのは辛い。。*/
そこで、以下のような Adapter を2つ用意する。
@FunctionalInterface
public interface LocalDateAdapter extends JsonSerializer<LocalDate>, JsonDeserializer<LocalDate> {
@Override
public default JsonElement serialize(LocalDate date, Type type, JsonSerializationContext context){
return new JsonPrimitive(date.format(getFormatter()));
}
@Override
public default LocalDate deserialize(JsonElement json, Type type, JsonDeserializationContext context) throws JsonParseException{
return LocalDate.parse(json.getAsString(), getFormatter());
}
public static LocalDateAdapter create(LocalDateAdapter a){
return a;
}
public DateTimeFormatter getFormatter();
}
続きを読む1年半くらい前、Wicket のTab ・・・org.apache.wicket.extensions.markup.html.tabs パッケージの一連のタブを使用する時のデザインを書いたことがある。
Wicket のタブページ作成作業 - Oboe吹きプログラマの黙示録
この時デザインの事しか書かなかった、実践でのTab のPanelインスタンスの状態として、書くべきことを書いていなかった。。
タブを切り替えて再度、一度表示したタブに戻ってきた時にフォーム入力したものなどを保持させたい場合があるはずだ。
最低限やらなければならないのは、AbstractTabの getPanel が一度生成したものを常に返すようにすること。そしてタブ切り替えの前に実行されることを捕捉して
保持を約束する。
例)
Panel tab1;
Panel tab2;
Panel tab3;
public TabedPage(){
List<ITab> tabs = new ArrayList<ITab>();
// tab1~3 まで生成済のTab Panelを返すようにする。
tabs.add(new AbstractTab(new Model<String>("Tab-1")){
@Override
public Panel getPanel(String panelId){
if (tab1==null) tab1 = new Tab1Panel(panelId);
return tab1;
}
});
queue(new AjaxTabbedPanel<ITab>("tabs", tabs));
}
タブの中で保持させるフォームの入力は、onchangeイベントあるいは、onblurイベントを捕捉して、ページインスタンスに保持させてしまう。
→ AjaxEventBehavior で捕まえて、コンポーネントの getInput を結果を、setModelObejct でセットして、AjaxRequestTarget の add メソッドを実行する!
当然、対象のコンポーネントは、setOutputMarkupId(true) を実行する。
タブ Panel の中で 入力テキストがある場合、例えば、、、
final TextField<String> name_Field = new TextField<String>("name", new Model<String>());
name_Field.setOutputMarkupId(true);
name_Field.add(new AjaxEventBehavior("change"){
@Override
protected void onEvent(AjaxRequestTarget target){
name_Field.setModelObject(name_Field.getInput());
target.add(name_Field);
}
});
queue(name_Field);
タブをクリックした時のイベントを捕捉しようとしたが、=onclickじゃなくて、、
Wincket が本来、Tab 専用にイベントを拾って処理してるところに、差し込んだ処理で書きたかったのだが、なかなかうまくいかないので、これで逃げることにした