All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.fizzed.crux.jackson.EnumStrategyModule Maven / Gradle / Ivy

There is a newer version: 1.0.48
Show newest version
package com.fizzed.crux.jackson;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonLocation;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.deser.BeanDeserializerModifier;
import com.fasterxml.jackson.databind.deser.Deserializers;
import com.fasterxml.jackson.databind.deser.std.StdKeyDeserializer;
import com.fasterxml.jackson.databind.deser.std.StdScalarDeserializer;
import com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException;
import com.fasterxml.jackson.databind.introspect.AnnotatedMethod;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.ser.Serializers;
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Objects;

/**
 * Module that helps deserialize enums accepting any case, while letting you
 * control how they are serialized (upper, lower, or retain).
 * 
 * @author jjlauer
 */
public class EnumStrategyModule extends SimpleModule {

    final private EnumMapper mapper;
    
    public EnumStrategyModule() {
        this(EnumSerializeStrategy.LOWER_CASE, EnumDeserializeStrategy.IGNORE_CASE);
    }
    
    public EnumStrategyModule(
            EnumSerializeStrategy serializeStrategy,
            EnumDeserializeStrategy deserializeStrategy) {

        Objects.requireNonNull(serializeStrategy, "serialize strategy was null");
        Objects.requireNonNull(deserializeStrategy, "deserialize strategy was null");
        
        this.mapper = new EnumMapper(serializeStrategy, deserializeStrategy);
    }

    @Override
    public void setupModule(final SetupContext context) {
        context.addDeserializers(new PermissiveEnumDeserializers(this.mapper));

        context.addSerializers(new Serializers.Base() {
            @Override
            public JsonSerializer findSerializer(SerializationConfig config, JavaType type, BeanDescription beanDesc) {
                if (type.isEnumType()) {
                    return new StdSerializer(Enum.class) {
                        @Override
                        public void serialize(Enum value, JsonGenerator jgen, SerializerProvider provider) throws IOException {
                            final String s = mapper.serialize(value);
                            jgen.writeString(s);
                        }
                    };
                }
                return null;
            }
        });

        context.addKeySerializers(new Serializers.Base() {
            @Override
            public JsonSerializer findSerializer(SerializationConfig config, JavaType type, BeanDescription beanDesc) {
                if (type.isEnumType()) {
                    return new StdSerializer(Enum.class) {
                        @Override
                        public void serialize(Enum value, JsonGenerator jgen, SerializerProvider provider) throws IOException {
                            final String s = mapper.serialize(value);
                            jgen.writeFieldName(s);
                        }
                    };
                }
                return null;
            }
        });
    }

    public EnumMapper getMapper() {
        return mapper;
    }

    public EnumStrategyModule setNullOnUnknown(
            boolean nullOnUnknown) {
        
        this.mapper.setNullOnUnknown(nullOnUnknown);
        return this;
    }
    
    public EnumStrategyModule setGlobalUnknownEnumHandler(
            EnumGlobalUnknownHandler globalUnknownEnumHandler) {
        
        this.mapper.setGlobalUnknownEnumHandler(globalUnknownEnumHandler);
        return this;
    }

    // Technique learned from https://github.com/dropwizard/dropwizard/tree/master/dropwizard-jackson/src/main/java/io/dropwizard/jackson
    static private class PermissiveEnumDeserializer extends StdScalarDeserializer> {
        private static final long serialVersionUID = 1L;

        private final EnumMapper mapper;

        @SuppressWarnings("unchecked")
        protected PermissiveEnumDeserializer(EnumMapper mapper, Class> clazz) {
            super(clazz);
            this.mapper = mapper;
        }

        @Override
        public Enum deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException {
            final String value = jp.getText();
            final Class rawClass = (Class)this.handledType();
            try {
                return (Enum)this.mapper.deserialize(value, rawClass);
            }
            catch (IOException e) {
                throw new UnrecognizedPropertyException(
                    jp, "No enum constant " + this.handledType().getCanonicalName() + "." + value,
                    JsonLocation.NA, rawClass, "value", null);
            }
        }

        @Override
        public boolean isCachable() {
            // Should cache enum deserializers similar to com.fasterxml.jackson.databind.deser.std.EnumDeserializer
            return true;
        }
    }

    static private class PermissiveEnumDeserializers extends Deserializers.Base {

        private final EnumMapper mapper;

        public PermissiveEnumDeserializers(EnumMapper mapper) {
            this.mapper = mapper;
        }

        @Override
        @SuppressWarnings("unchecked")
        public JsonDeserializer findEnumDeserializer(Class type,
                                                        DeserializationConfig config,
                                                        BeanDescription desc) throws JsonMappingException {
            // If the user configured to use `toString` method to deserialize enums
            if (config.hasDeserializationFeatures(DeserializationFeature.READ_ENUMS_USING_TO_STRING.getMask()) ||
                    config.hasDeserializationFeatures(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_AS_NULL.getMask()) ||
                    // The presence of @JsonEnumDefaultValue will cause a fallback to the default, however lets short circuit here
                    config.hasDeserializationFeatures(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_USING_DEFAULT_VALUE.getMask())) {
                return null;
            }

            // If there is a JsonCreator annotation we should use that instead of the PermissiveEnumDeserializer
            final Collection factoryMethods = desc.getFactoryMethods();
            if (factoryMethods != null) {
                for (AnnotatedMethod am : factoryMethods) {
                    if (am.hasAnnotation(JsonCreator.class)) {
                        return null;
                    }
                }
            }

            // If any enum choice is annotated with an annotation from jackson, defer to
            // Jackson to do the deserialization
            for (Field field : type.getFields()) {
                for (Annotation annotation : field.getAnnotations()) {
                    final String packageName = annotation.annotationType().getPackage().getName();
                    if (packageName.equals("com.fasterxml.jackson.annotation")) {
                        return null;
                    }
                }
            }

            return new PermissiveEnumDeserializer(mapper, (Class>)type);
        }
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy