技術ネタはQiitaに移りました。壁もどこぞに。

Spring で JSON null 値をフィールド型ごとに変換する

せっかく仕事が休みなので、ためていた分を書き残しておこうと思う。

Spring での JSON 変換に、Jackson が使用されているのはどなたもご存知のところかと思う。ところが、null 値が設定されていた場合に、レスポンスをフィールドの型ごとに変換したいような場合の例が非常に少ない。

そこで今回は、Spring Boot を使用したうえで、そんな要件があった場合のコード例を残しておこうと思う。以下全文。

@Configuration
public class JacksonConfiguration {
    protected static final String FILTER_ID = "PropertyConversionFilter";

    @Bean
    @Primary
    @ConditionalOnProperty(prefix = "application.jackson.null-conversion",
            name = "enabled", matchIfMissing = true)
    public ObjectMapper objectMapper() {
        ObjectMapper objectMapper = new ObjectMapper();

        SimpleFilterProvider filterProvider = new SimpleFilterProvider();
        filterProvider.addFilter(FILTER_ID, new PropertyConversionFilter());

        objectMapper.setFilterProvider(filterProvider);
        objectMapper.registerModule(new PropertyConverterModule());

        return objectMapper;
    }

    @JsonFilter(FILTER_ID)
    protected static class PropertyConverterMixIn {}

    protected static class PropertyConverterModule extends SimpleModule {
        @Override
        public void setupModule(SetupContext context) {
            context.setMixInAnnotations(Object.class, PropertyConverterMixIn.class);
        }
    }

    protected static class PropertyConversionFilter extends SimpleBeanPropertyFilter {
        @Override
        public void serializeAsField(Object pojo, JsonGenerator jgen, SerializerProvider provider,
                PropertyWriter writer) throws Exception {

            if (!(writer instanceof BeanPropertyWriter)) {
                super.serializeAsField(pojo, jgen, provider, writer);
                return;
            }

            BeanPropertyWriter beanPropertyWriter = (BeanPropertyWriter) writer;
            JavaType javaType = beanPropertyWriter.getType();
            Class<?> type = javaType.getRawClass();
            String name = beanPropertyWriter.getName();
            Object value = beanPropertyWriter.get(pojo);

            if (value != null) {
                super.serializeAsField(pojo, jgen, provider, writer);
                return;
            }

            if (Number.class.isAssignableFrom(type)) {
                jgen.writeNumberField(name, 0);
                return;
            }

            if (Boolean.class.isAssignableFrom(type)) {
                jgen.writeBooleanField(name, false);
                return;
            }

          if (String.class.isAssignableFrom(type) || Character.class.isAssignableFrom(type)) {
                jgen.writeStringField(name, "");
                return;
            }

            if (Collection.class.isAssignableFrom(type) || javaType.isArrayType()) {
                jgen.writeArrayFieldStart(name);
                jgen.writeEndArray();
                return;
            }

            super.serializeAsField(pojo, jgen, provider, writer);
        }
    }
}

せっかく Spring Boot を使用しているので、@ConditionalOnProperty アノテーションを使用して、この設定の有効無効を切り替えられるようにしてある。application.propertiesapplication.jackson.null-conversion.enable=true としておけば、レスポンス時のフィールド型に応じた、null 値の変換を行ってくれるようになる。

今回の例であれば、数値型は 0 に、論理型は false に、文字型および文字列型は "" に、それぞれフィールド値が変換されるようになっている。同様に型ごとの値変換処理を追記してやればいくらでも応用がきくので、こんなことも出来るということを覚えておくとどこかで使いどころがありそう。