SiNBLOG

140文字に入らないことを、極稀に書くBlog

Slim3でModelをJsonに加工する方法を考える

Slim3Ajax用のレスポンスをJsonで返す時の方法について、あれこれと悩んでた。

Slim3にはMeta.modelToJson()があるので、始めはこれを使えば良いと思っていた。
しかし、いくつかやりたいことで、できないことがあることが分かった。

・getter,setterだけでは値を取得しない
・persistent = falseの値を取得しない


1つ目のgetter,setterだけでは値を取得しない
フィールドの値を表示用に加工したりしたかったのだけど、こいつができないorz
JSPのノリでgetterを作っていたのだけど、ダメだった。

また、Keyの値に入れているものを表示用に出したりもしてやりたかったのだけど、同じ理由でできない。


2つ目のpersistent=falseの値を取得しない
getter,setterだけでダメなら、Datastoreに入れないフィールドにしてやろう!と思ったけど・・・。
これでも値を取得してくれなかった。
どうやらEntityとして生成されるフィールドのみ取得するみたいだ。


それらを試してみたソースは以下の通り。

Model


package org.sinsandbox.model;

import java.io.Serializable;
import java.util.Date;

import com.google.appengine.api.datastore.Key;

import org.slim3.datastore.Attribute;
import org.slim3.datastore.Model;

/**
* Json変換時の振る舞いを確認するためのModel
*
* @author sinmetal
*
*/
@Model(schemaVersion = 1)
public class JsonConduct implements Serializable {

private static final long serialVersionUID = 1L;

@Attribute(primaryKey = true)
private Key key;

@Attribute(version = true)
private Long version;

@Attribute(persistent = false)
private String value;

private Date entryDate;

/**
* Returns the key.
*
* @return the key
*/
public Key getKey() {
return key;
}

/**
* Sets the key.
*
* @param key
* the key
*/
public void setKey(Key key) {
this.key = key;
}

/**
* Returns the version.
*
* @return the version
*/
public Long getVersion() {
return version;
}

/**
* Sets the version.
*
* @param version
* the version
*/
public void setVersion(Long version) {
this.version = version;
}

/**
* Get name
*
* @return key name
*/
public String getName() {
if (key == null) {
return "";
}
return key.getName();
}

/**
* Set name
*
* @param name
*/
public void setName(String name) {

}

/**
* @return the value
*/
public String getValue() {
return value;
}

/**
* @param value the value to set
*/
public void setValue(String value) {
this.value = value;
}

/**
* @return the entryDate
*/
public Date getEntryDate() {
return entryDate;
}

/**
* @param entryDate the entryDate to set
*/
public void setEntryDate(Date entryDate) {
this.entryDate = entryDate;
}

@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((key == null) ? 0 : key.hashCode());
return result;
}

@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
JsonConduct other = (JsonConduct) obj;
if (key == null) {
if (other.key != null) {
return false;
}
} else if (!key.equals(other.key)) {
return false;
}
return true;
}
}


ModelのUnitTest


package org.sinsandbox.model;

import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;

import java.lang.reflect.Type;
import java.util.Date;
import java.util.Map;

import org.junit.Before;
import org.junit.Test;
import org.sinsandbox.meta.JsonConductMeta;
import org.slim3.datastore.Datastore;
import org.slim3.tester.AppEngineTestCase;

import com.google.appengine.api.datastore.Key;
import com.google.appengine.api.datastore.KeyFactory;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;

/**
* Json変換時の振る舞いを確認
*
* @author sinmetal
*
*/
public class JsonConductTest extends AppEngineTestCase {

private JsonConduct model = new JsonConduct();

private JsonConductMeta meta = JsonConductMeta.get();

private Map map;

@Before
public void setUp() throws Exception {
super.setUp();

Key key = Datastore.createKey(meta, "hoge");
model.setKey(key);
model.setVersion(1L);
model.setValue("fuga");
model.setEntryDate(new Date());

String json = meta.modelToJson(model);

Gson gson = new Gson();
Type mapType = new TypeToken>() {
}.getType();
map = gson.fromJson(json, mapType);
}

@Test
public void Json変換テスト() throws Exception {
String keyString = KeyFactory.keyToString(model.getKey());
assertThat(map.get("key").toString(), is(keyString));
assertThat(
keyString,
is("agpVbml0IFRlc3RzchULEgtKc29uQ29uZHVjdCIEaG9nZQw"));
assertThat(map.get("version").toString(), is("1.0"));
assertThat(map.containsKey("name"), is(false)); //getter, setterだけでは生成されない
assertThat(map.containsKey("value"), is(false)); //persistent=falseでも生成されない
}
}


さてさて、どうしてやろうかと思って考えたのだけど、表示用のBeanを作ってやるしか思いつかなかった。
そうして、作ったのが以下。

Modelを表示するために加工するView


package org.sinsandbox.view;

import org.sinsandbox.model.JsonConduct;
import org.sinsandbox.util.KeyConverter;
import org.slim3.util.BeanUtil;
import org.slim3.util.CopyOptions;

/**
* JsonConductModelを表示する時に加工するためのView
*
* @author sinmetal
*
*/
public class JsonConductView {

/** key */
private String key;
/** version */
private Long version;
/** name */
private String name;
/** value */
private String value;
/** 登録日時 */
private String entryDate;

/**
* @return the key
*/
public String getKey() {
return key;
}

/**
* @param key
* the key to set
*/
public void setKey(String key) {
this.key = key;
}

/**
* @return the version
*/
public Long getVersion() {
return version;
}

/**
* @param version
* the version to set
*/
public void setVersion(Long version) {
this.version = version;
}

/**
* @return the name
*/
public String getName() {
return name;
}

/**
* @param name
* the name to set
*/
public void setName(String name) {
this.name = name;
}

/**
* @return the value
*/
public String getValue() {
return value;
}

/**
* @param value
* the value to set
*/
public void setValue(String value) {
this.value = value;
}

/**
* @return the entryDate
*/
public String getEntryDate() {
return entryDate;
}

/**
* @param entryDate
* the entryDate to set
*/
public void setEntryDate(String entryDate) {
this.entryDate = entryDate;
}

/**
* インスタンス生成
*
* @param model
* @return modelの情報を設定したview
*/
public static JsonConductView getInstance(JsonConduct model) {
JsonConductView instance = new JsonConductView();
CopyOptions options = new CopyOptions();
options.converter(new KeyConverter(), "key");
options.dateConverter("yyyy/MM/dd", "entryDate");
BeanUtil.copy(model, instance, options);
return instance;
}
}


BeanUtil.copy()の時にKeyを表示用に変換するためのConverter
Slim3のBeanUtilには、Convertする時の振る舞いを追加できるので、KeyのためのConverterを作った。


package org.sinsandbox.util;

import org.slim3.util.Converter;

import com.google.appengine.api.datastore.Key;
import com.google.appengine.api.datastore.KeyFactory;

/**
* The converter for key.
*
* @author sinmetal
*
*/
public class KeyConverter implements Converter {

@Override
public Key getAsObject(String value) {
return KeyFactory.stringToKey(value);
}

@Override
public String getAsString(Object value) {
if (value == null) {
return null;
}
if (!(value instanceof Key)) {
throw new IllegalArgumentException("The class("
+ value.getClass().getName()
+ ") can not be assigned to date.");
}
return KeyFactory.keyToString((Key) value);
}

@Override
public boolean isTarget(Class clazz) {
return Key.class.isAssignableFrom(clazz);
}

}


ViewのUnitTest


package org.sinsandbox.view;

import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;

import java.lang.reflect.Type;
import java.util.Date;
import java.util.Map;

import org.junit.Before;
import org.junit.Test;
import org.sinsandbox.meta.JsonConductMeta;
import org.sinsandbox.model.JsonConduct;
import org.slim3.datastore.Datastore;
import org.slim3.tester.AppEngineTestCase;

import com.google.appengine.api.datastore.Key;
import com.google.appengine.api.datastore.KeyFactory;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;

/**
* Json変換時の振る舞いを確認するためのModelを表示用に加工するView
*
* @author sinmetal
*
*/
public class JsonConductViewTest extends AppEngineTestCase {
private JsonConduct model = new JsonConduct();

private JsonConductMeta meta = JsonConductMeta.get();

private Map map;

@Before
public void setUp() throws Exception {
super.setUp();

Key key = Datastore.createKey(meta, "hoge");
model.setKey(key);
model.setVersion(1L);
model.setValue("fuga");
model.setEntryDate(new Date());

Gson gson = new Gson();
JsonConductView view = JsonConductView.getInstance(model);
String json = gson.toJson(view);

Type mapType = new TypeToken>() {
}.getType();
map = gson.fromJson(json, mapType);
}

@Test
public void Json変換テスト() throws Exception {
String keyString = KeyFactory.keyToString(model.getKey());
assertThat(map.get("key").toString(), is(keyString));
assertThat(
keyString,
is("agpVbml0IFRlc3RzchULEgtKc29uQ29uZHVjdCIEaG9nZQw"));
assertThat(map.get("version").toString(), is("1.0"));
final String NAME = "name";
assertThat(map.containsKey(NAME), is(true));
assertThat(map.get(NAME).toString(), is(model.getName()));
final String VALUE = "value";
assertThat(map.containsKey(VALUE), is(true));
assertThat(map.get(VALUE).toString(), is(model.getValue()));
}
}


ちょっと作ってやるのが面倒だけど、とりあえずこんな感じで切り抜けようかと思う!