jax-rs を使用して、JSON をカスタム Java オブジェクトに手動でアンマーシャリングする方法がわかりません。
ブラウザから、次の JSON を使用して単純な put リクエストを送信しています。
{"myDate":{"dayOfMonth":23, "monthOfYear":7, "year":2011}}
サーバーには、この JSON を使用して Java オブジェクトのプロパティを出力する BlahResource があります。
@Component
@Scope("request")
@Path("/blah")
@Consumes("application/json")
@Produces("application/json")
public class BlahResource {
@PUT
public String putBlah(Blah blah) {
System.out.println("Value: " + blah.getMyDate().getMonthOfYear() + "/" + blah.getMyDate().getDayOfMonth() + "/" + blah.getMyDate().getYear());
return "{}";
}
}
Blah のソース コードは次のとおりです。
public class Blah {
private LocalDate myDate;
public Blah()
{
}
public void setMyDate(LocalDate myDate)
{
this.myDate = myDate;
}
public LocalDate getMyDate()
{
return myDate;
}
}
問題は、Blah.myDate が Joda 時間の LocalDate クラスであり、dayOfMonth、monthOfYear、および year のセッターがないことです。たとえば、これを実行すると、次の例外がスローされます。
Jul 10, 2011 8:40:33 AM
com.sun.jersey.spi.container.ContainerResponse mapMappableContainerException
SEVERE: The exception contained within MappableContainerException could not
be mapped to a response, re-throwing to the HTTP container
org.codehaus.jackson.map.exc.UnrecognizedPropertyException:
Unrecognized field "dayOfMonth"
これは私にとって完全に理にかなっています。問題は、タイプ LocalDate が検出されるたびに、JSON を LocalDate に変換するためにアダプター クラスが使用されるように、ある種のアダプターを作成する方法がわからないことです。
理想的には、次のようなことをしたい:
public class LocalDateAdapter {
public LocalDate convert(String json)
{
int dayOfMonth = (Integer)SomeJsonUtility.extract("dayOfMonth");
int year = (Integer)SomeJsonUtility.extract("year");
int monthOfYear = (Integer)SomeJsonUtility.extract("monthOfYear");
return new LocalDate(year, monthOfYear, dayOfMonth);
}
}
アップデート
現在、2 つの方法を試しましたが、どちらも機能していないようです。
1) ObjectMapper の使用
ObjectMapper のハンドルを取得し、デシリアライザーを追加するだけでよいようです。そこで、このプロバイダーを作成しました。驚いたことに、dserializer に LocalDateDeserializer という名前を付けました。Eclipse の自動修正インポートを行ったとき、Jackson が既に Joda の拡張機能を提供していることにショックを受けました。サーバーを起動すると、プロバイダーが見つかりますが、それ以外の場合、このコードは呼び出されないようです。
import javax.ws.rs.ext.ContextResolver;
import javax.ws.rs.ext.Provider;
import org.codehaus.jackson.Version;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.map.ext.JodaDeserializers.LocalDateDeserializer;
import org.codehaus.jackson.map.module.SimpleModule;
import org.joda.time.LocalDate;
import org.springframework.stereotype.Component;
@Component
@Provider
public class ObjectMapperProvider implements ContextResolver<ObjectMapper> {
@Override
public ObjectMapper getContext(Class<?> type) {
ObjectMapper mapper = new ObjectMapper();
SimpleModule testModule = new SimpleModule("MyModule", new Version(1, 0, 0, null))
.addDeserializer(LocalDate.class, new LocalDateDeserializer());
mapper.registerModule(testModule);
return mapper;
}
}
2) 私が試した 2 番目の方法は、@JsonDeserialize アノテーションをフィールドに直接指定することです。
@JsonDeserialize(using = CustomDateDeserializer.class)
private LocalDate myDate;
これも呼び出されていないようです。
public class CustomDateDeserializer extends JsonDeserializer<LocalDate> {
@Override
public LocalDate deserialize(JsonParser parser, DeserializationContext context) throws IOException, JsonProcessingException
{
return new LocalDate(2008, 2, 5);
}
}
どうすればいいのかわからない。これは非常に基本的な問題のようです。
更新 2
逆シリアル化を使用するために Jackson を削除することを検討しています (ただし、Jersey ではかなりうまく機能します)。
私はすでにシリアライゼーションに flexjson を使用していましたが、デシリアライゼーションは flexjson と同じくらい単純なようです。これらの他のすべてのライブラリには、かなりの抽象化と不必要な複雑さがあります。
Flexjson では、ObjectFactory を実装する必要がありました。
class LocalDateTransformer implements ObjectFactory {
@Override
public Object instantiate(ObjectBinder context, Object value, Type targetType, Class targetClass)
{
HashMap map = (HashMap)value;
int year = (Integer)map.get("year");
int monthOfYear = (Integer)map.get("monthOfYear");
int dayOfMonth = (Integer)map.get("dayOfMonth");
return new LocalDate(year, monthOfYear, dayOfMonth);
}
}
最初に投稿した「アダプター」クラスに驚くほど似ています。そして、私のリソースメソッドは次のようになります:
@PUT
public String putBlah(String blahStr) {
Blah blah = new JSONDeserializer<Blah>().use(LocalDate.class, new LocalDateTransformer()).deserialize(blahStr, Blah.class);
}