Blog

jackson で JDK8 の Optional をうまく扱ってほしい

jackson では JDK8 の Optional にまだ対応していません。 Jackson 自体が JDK6 をサポートしているので、コアのデータバインディングに入ることはないのですが、それにしても JDK8 の Optional に対応していないのは不便ですね。まあ JDK8 はまだあれなので、しょうがないですが。。

しかしまあ、JDK8 使ってると Optional を返したいときはままあるので、はやく jackson-databind-jdk8 が出ることを願ってやみません。

取り急ぎ、https://github.com/FasterXML/jackson-databind/issues/494 この issue に書いてある方法で、対応は可能。これをコピペッペして、使えば動きます。

jersey でカスタマイズした ObjectMapper を使う方法についてはこちらを御覧ください。 https://jersey.java.net/documentation/latest/media.html#json.jackson

package me.geso.mobirc4j;

import java.io.IOException;
import java.util.Optional;

import javax.ws.rs.ext.ContextResolver;

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.Version;
import com.fasterxml.jackson.databind.BeanProperty;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.deser.ContextualDeserializer;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.ser.std.StdSerializer;

/**
 * Module for JDK 1.8 types.
 * 
 * <p>
 * https://github.com/FasterXML/jackson-databind/issues/494
 * 
 * @author Gili Tzabari
 */
final class Jdk18Module extends SimpleModule
{
	private static final long serialVersionUID = 1L;

	private static class OptionalSerializer extends StdSerializer<Optional<?>>
	{
		/**
		 * Create a new OptionalSerializer.
		 */
		OptionalSerializer()
		{
			super(Optional.class, true);
		}

		@Override
		public void serialize(Optional<?> value, JsonGenerator generator,
				SerializerProvider provider)
				throws IOException
		{
			if (value.isPresent())
				generator.writeObject(value.get());
			else
				generator.writeNull();
		}
	}

	private static class OptionalDeserializer extends
			StdDeserializer<Optional<?>>
			implements ContextualDeserializer
	{
		private static final long serialVersionUID = 1L;
		private Class<?> targetClass;

		/**
		 * Creates a new OptionalDeserializer.
		 */
		OptionalDeserializer()
		{
			super(Optional.class);
		}

		@Override
		public JsonDeserializer<?> createContextual(
				DeserializationContext context,
				BeanProperty property) throws JsonMappingException
		{
			if (property != null)
			{
				// See
				// http://jackson-users.ning.com/forum/topics/deserialize-with-generic-type
				JavaType type = property.getType();
				JavaType ofType = type.containedType(0);
				this.targetClass = ofType.getRawClass();
			}
			return this;
		}

		@Override
		public Optional<?> deserialize(JsonParser parser,
				DeserializationContext context)
				throws IOException, JsonProcessingException
		{
			return Optional.of(parser.readValueAs(targetClass));
		}

		@Override
		public Optional<?> getNullValue()
		{
			return Optional.empty();
		}
	}

	/**
	 * Creates a new Jdk18Module.
	 */
	public Jdk18Module()
	{
		super("Jdk18Module", new Version(1, 0, 0, null,
				"com.fasterxml.jackson.databind", "jdk18"));
		addSerializer(new OptionalSerializer());
		addDeserializer(Optional.class, new OptionalDeserializer());
	}
}

public class MyObjectMapperProvider implements ContextResolver<ObjectMapper> {
	final ObjectMapper objectMapper;

	public MyObjectMapperProvider() {
		objectMapper = new ObjectMapper();
		objectMapper.registerModule(new Jdk18Module());
	}

	@Override
	public ObjectMapper getContext(Class<?> type) {
		return objectMapper;
	}
}

としておいて、以下のようにリソースコンフィグに登録すればよいです。

ResourceConfig resourceConfig = new ResourceConfig();
resourceConfig.property(ServerProperties.BV_SEND_ERROR_IN_RESPONSE, true);
resourceConfig.property(ServerProperties.BV_DISABLE_VALIDATE_ON_EXECUTABLE_OVERRIDE_CHECK, true);
resourceConfig.register(MyObjectMapperProvider.class);
resourceConfig.packages(ServerController.class.getPackage().getName());
resourceConfig.register(JacksonFeature.class);

ちなみに、こういう方法いれないと、単に OpionalLong#getLong() とかをガッツリ呼んじゃって、例外があがって悲しみにくれることになります。