JavaのJAX-RSと.NETのAutoRest/Microsoft.REST.ClientRuntimeを使ってRESTを使った連携プログラムを書いているときに、日付データ(JSONのtype=”string” format=”date-time”やformat=”date”なデータ)のシリアライズ/デシリアライズが初期状態では意図したとおりに動かないことがあったので、カスタマイズしたデシリアライザを書いた時のメモです。
プログラム中では日付書式にミリ秒を含めた形で定義をしていますが、RESTのサービス側とクライアント側で日付書式を合わせればミリ秒部分を削りISO 8601形式に統一しても問題がないと思います。
また、日付の書式をJsonFormatアノテーションから取得していますが、デシリアライザ中の取得先を変えることほかのアノテーションや方法で取得することができると思います。
※2020/05/26追記
日付書式をJava側では”yyyy-MM-dd’T’HH:mm:ssXXX”、.NET側では”yyyy-MM-dd’T’HH:mm:sszzz”と指定することで、ISO 8601の拡張形式でやり取りができることを確認しました。
package com.ria_lab.json; import java.io.IOException; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.BeanProperty; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.JsonDeserializer; import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.deser.ContextualDeserializer; /** * JSONの日付データをパースしjava.util.Dateに変換するデシリアライザ */ public class CustomDateDeserializer extends JsonDeserializer<Date> implements ContextualDeserializer { /** デフォルトの日時フォーマット */ private static final SimpleDateFormat DEFAULT_FORMAT = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSXXX"); /** デシリアライズに使用するフォーマット */ private SimpleDateFormat format; /** * @see com.fasterxml.jackson.databind.deser.ContextualDeserializer#createContextual(com.fasterxml.jackson.databind.DeserializationContext, com.fasterxml.jackson.databind.BeanProperty) */ @Override public JsonDeserializer<?> createContextual(DeserializationContext ctxt, BeanProperty property) throws JsonMappingException { // JsonFormatアノテーションが指定されていないかチェックし、 // 使用されている場合はパターンをデシリアライズのフォーマットとし // て使用する。指定されていない場合、デフォルトを使用 JsonFormat jsonFormat = property.getAnnotation(JsonFormat.class); format = (jsonFormat == null) ? DEFAULT_FORMAT : new SimpleDateFormat(jsonFormat.pattern()); return this; } /** * @see com.fasterxml.jackson.databind.JsonDeserializer#deserialize(com.fasterxml.jackson.core.JsonParser, com.fasterxml.jackson.databind.DeserializationContext) */ @Override public Date deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException { try { return format.parse(p.getText()); } catch (ParseException e) { throw new IllegalStateException(e); } } }
デシリアライザは、Beanのプロパティ変数宣言部分にアノテーションを記述することで有効化されるはずです。
しかし、作成したBeanを含むRESTサービスを.NETのAutoRestを使用して.NET側のRESTクライアントを生成すると、プロパティが二重に生成されるなど意図しない変換が行われる場合があります。(Java側のプロパティ/変数の命名が正しく変換されない)
これを回避するためsetterにアノテーションを記述して解決しています。
package com.ria_lab.bean;
import com.ria_lab.json.CustomDateDeserializer; import java.util.Date; import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.ria_lab.json.CustomDateDeserializer; /** * テスト用のBean * birthdayプロパティのsetterのアノテーションでデシリアライザを指定 */ public class TestBean extends implements java.io.Serializable { private Date birthday; public TestBean() { } public Date getBirthday() { return this.birthday; } @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSXXX") @JsonDeserialize(using=CustomDateDeserializer.class) public void setBirthday(Date birthday) { this.birthday = birthday; } }
プロパティごとにデシリアライザを設定するのが面倒だという場合、カスタマイズしたJacksonのObjectMapperを用意することで一括指定を行うこともできます。
package com.ria_lab.json; import java.text.SimpleDateFormat; import java.util.Date; import javax.ws.rs.ext.ContextResolver;
import javax.ws.rs.ext.Provider;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; import com.fasterxml.jackson.databind.module.SimpleModule; /** * カスタマイズしたJacksonのObjectMapper */
@Provider public class CustomJacksonConfig implements ContextResolver<ObjectMapper> { private final ObjectMapper mapper = new ObjectMapper(); /** * Constructor */ public CustomJacksonConfig() { mapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZZZ")); mapper.enable(SerializationFeature.INDENT_OUTPUT); SimpleModule module = new SimpleModule(); module.addDeserializer(Date.class, new CustomDateDeserializer()); mapper.registerModule(module); } /** * @see javax.ws.rs.ext.ContextResolver#getContext(java.lang.Class) */ @Override public ObjectMapper getContext(Class<?> type) { return mapper; } }
.NET側では日付変換の設定を以下のように設定しています。
var restclient = new RLRestService.RLRestServiceClient(new System.Uri("http://localhost/restsample/"), new Microsoft.Rest.BasicAuthenticationCredentials()); restclient.SerializationSettings.Converters.Add(new Newtonsoft.Json.Converters.IsoDateTimeConverter() { DateTimeFormat = "yyyy-MM-dd'T'HH:mm:ss.fffzzz" });
Please give us your valuable comment