Jackson databind enumの大文字と小文字を区別しません


99

大文字と小文字を区別しない列挙値を含むJSON文字列をデシリアライズするにはどうすればよいですか?(Jackson Databindを使用)

JSON文字列:

[{"url": "foo", "type": "json"}]

そして私のJava POJO:

public static class Endpoint {

    public enum DataType {
        JSON, HTML
    }

    public String url;
    public DataType type;

    public Endpoint() {

    }

}

この場合、JSONの逆シリアル化は、"type":"json"機能する場所で失敗し"type":"JSON"ます。しかし、私"json"は命名規則の理由からも働きたいです。

POJOをシリアル化すると、大文字になります "type":"JSON"

@JsonCreator@JsonGetter の使用を考えました:

    @JsonCreator
    private Endpoint(@JsonProperty("name") String url, @JsonProperty("type") String type) {
        this.url = url;
        this.type = DataType.valueOf(type.toUpperCase());
    }

    //....
    @JsonGetter
    private String getType() {
        return type.name().toLowerCase();
    }

そしてそれはうまくいった。しかし、これは私にとってハックのように見えるので、より良い解決策があるかどうか疑問に思っていました。

カスタムデシリアライザを作成することもできますが、列挙型を使用するさまざまなPOJOを入手したため、メンテナンスが困難になります。

適切な命名規則で列挙型をシリアル化および逆シリアル化するより良い方法を誰かが提案できますか?

Javaの列挙型を小文字にしたくない!

ここに私が使用したいくつかのテストコードがあります:

    String data = "[{\"url\":\"foo\", \"type\":\"json\"}]";
    Endpoint[] arr = new ObjectMapper().readValue(data, Endpoint[].class);
        System.out.println("POJO[]->" + Arrays.toString(arr));
        System.out.println("JSON ->" + new ObjectMapper().writeValueAsString(arr));

どのバージョンのジャクソンを使用していますか?このJIRA jira.codehaus.org/browse/JACKSON-861をご覧ください
アレクセイガブリ

私はジャクソン2.2.3を使用しています
tom91136 2014年

OK 2.4.0-RC3に更新しました
tom91136

回答:


38

バージョン2.4.0では、すべてのEnumタイプのカスタムシリアライザーを登録できます(githubの問題へのリンク)。また、Enumタイプを認識する標準のEnumデシリアライザを自分で置き換えることもできます。次に例を示します。

public class JacksonEnum {

    public static enum DataType {
        JSON, HTML
    }

    public static void main(String[] args) throws IOException {
        List<DataType> types = Arrays.asList(JSON, HTML);
        ObjectMapper mapper = new ObjectMapper();
        SimpleModule module = new SimpleModule();
        module.setDeserializerModifier(new BeanDeserializerModifier() {
            @Override
            public JsonDeserializer<Enum> modifyEnumDeserializer(DeserializationConfig config,
                                                              final JavaType type,
                                                              BeanDescription beanDesc,
                                                              final JsonDeserializer<?> deserializer) {
                return new JsonDeserializer<Enum>() {
                    @Override
                    public Enum deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException {
                        Class<? extends Enum> rawClass = (Class<Enum<?>>) type.getRawClass();
                        return Enum.valueOf(rawClass, jp.getValueAsString().toUpperCase());
                    }
                };
            }
        });
        module.addSerializer(Enum.class, new StdSerializer<Enum>(Enum.class) {
            @Override
            public void serialize(Enum value, JsonGenerator jgen, SerializerProvider provider) throws IOException {
                jgen.writeString(value.name().toLowerCase());
            }
        });
        mapper.registerModule(module);
        String json = mapper.writeValueAsString(types);
        System.out.println(json);
        List<DataType> types2 = mapper.readValue(json, new TypeReference<List<DataType>>() {});
        System.out.println(types2);
    }
}

出力:

["json","html"]
[JSON, HTML]

1
ありがとう、POJOのボイラープレートをすべて削除できるようになりました:)
tom91136

私は自分のプロジェクトでこれを個人的に主張しています。私の例を見ると、多くの定型コードが必要です。分離/シリアル化に別の属性を使用する利点の1つは、Javaの重要な値(列挙型の名前)の名前をクライアントの重要な値(きれいな印刷)に分離することです。たとえば、HTML DataTypeをHTML_DATA_TYPEに変更する必要がある場合、キーが指定されていれば、外部APIに影響を与えることなく変更できます。
Sam Berry

1
これは良いスタートですが、列挙型がJsonPropertyまたはJsonCreatorを使用している場合は失敗します。Dropwizardには、より堅牢な実装であるFuzzyEnumModuleがあります。
Pixel Elephant

134

ジャクソン2.9

これは非常に簡単で、jackson-databind2.9.0 以降を使用しています

ObjectMapper objectMapper = new ObjectMapper();
objectMapper.enable(MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS);

// objectMapper now deserializes enums in a case-insensitive manner

テストの完全な例

import com.fasterxml.jackson.databind.MapperFeature;
import com.fasterxml.jackson.databind.ObjectMapper;

public class Main {

  private enum TestEnum { ONE }
  private static class TestObject { public TestEnum testEnum; }

  public static void main (String[] args) {
    ObjectMapper objectMapper = new ObjectMapper();
    objectMapper.enable(MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS);

    try {
      TestObject uppercase = 
        objectMapper.readValue("{ \"testEnum\": \"ONE\" }", TestObject.class);
      TestObject lowercase = 
        objectMapper.readValue("{ \"testEnum\": \"one\" }", TestObject.class);
      TestObject mixedcase = 
        objectMapper.readValue("{ \"testEnum\": \"oNe\" }", TestObject.class);

      if (uppercase.testEnum != TestEnum.ONE) throw new Exception("cannot deserialize uppercase value");
      if (lowercase.testEnum != TestEnum.ONE) throw new Exception("cannot deserialize lowercase value");
      if (mixedcase.testEnum != TestEnum.ONE) throw new Exception("cannot deserialize mixedcase value");

      System.out.println("Success: all deserializations worked");
    } catch (Exception e) {
      e.printStackTrace();
    }
  }
}

3
これは金です!
Vikas Prasad 2017

8
2.9.2を使用していますが、機能しません。原因:com.fasterxml.jackson.databind.exc.InvalidFormatException:タイプ.... Gender`の値を文字列「male」からデシリアライズできません:値が宣言されたEnumインスタンス名の1つではありません:[FAMALE、MALE]
Jordan Silva

@JordanSilvaは確かにv2.9.2で動作します。検証用のテストを含む完全なコード例を追加しました。あなたのケースで何が起こったのかわかりませんが、jackson-databind2.9.2でサンプルコードを実行すると、特に期待どおりに動作します。
davnicwil 2018

5
スプリングブートを使用して、プロパティを追加するだけですspring.jackson.mapper.accept-case-insensitive-enums=true
Arne Burmeister

1
@JordanSilva多分あなたは私がしたようにパラメータを取得する際に列挙型をデシリアライズしようとしていますか?=)私は私の問題を解決し、ここで答えました。それがお役に立て
ば幸い

83

私のプロジェクトでこれと同じ問題に遭遇し、文字列キーと列挙型を使用して列挙型を構築し、@JsonValueそれぞれシリアル化と非シリアル化の静的コンストラクターを作成することにしました。

public enum DataType {
    JSON("json"), 
    HTML("html");

    private String key;

    DataType(String key) {
        this.key = key;
    }

    @JsonCreator
    public static DataType fromString(String key) {
        return key == null
                ? null
                : DataType.valueOf(key.toUpperCase());
    }

    @JsonValue
    public String getKey() {
        return key;
    }
}

1
これはDataType.valueOf(key.toUpperCase())そうでなければなりません-そうでなければ、あなたは本当に何も変更していません。NPEを回避するための防御的なコーディング:return (null == key ? null : DataType.valueOf(key.toUpperCase()))
sarumont

2
@sarumontをしっかりキャッチ。編集を行いました。また、メソッドの名前を「fromString」に変更して、JAX-RSとうまく連携できるようにしました。
Sam Berry

1
私はこのアプローチが好きでしたが、詳細度の低いバリアントを選びました。以下を参照してください。
linqu

2
明らかにkeyフィールドは不要です。ではgetKey、次のことができますreturn name().toLowerCase()
yair

1
enumにjsonの名前とは異なる名前を付けたい場合は、キーフィールドが好きです。私の場合、レガシーシステムは、送信する値の本当に省略された覚えにくい名前を送信します。このフィールドを使用して、Java列挙型のより適切な名前に変換できます。
グリンチ

47

ジャクソン2.6以降、これを簡単に実行できます。

    public enum DataType {
        @JsonProperty("json")
        JSON,
        @JsonProperty("html")
        HTML
    }

完全な例については、この要点を参照してください。


26
これを行うと問題が元に戻ることに注意してください。現在、ジャクソンは小文字のみを受け入れ、大文字または大/小文字混合の値を拒否します。
Pixel Elephant

30

私はSam B.の解決策を模索しましたが、より単純な変形です。

public enum Type {
    PIZZA, APPLE, PEAR, SOUP;

    @JsonCreator
    public static Type fromString(String key) {
        for(Type type : Type.values()) {
            if(type.name().equalsIgnoreCase(key)) {
                return type;
            }
        }
        return null;
    }
}

これは簡単だとは思いません。DataType.valueOf(key.toUpperCase())ループがある直接のインスタンス化です。これは、非常に多数の列挙型にとって問題になる可能性があります。もちろん、valueOfコードが回避するIllegalArgumentExceptionをスローする可能性があるので、例外チェックよりもnullチェックを好む場合は、これは大きな利点です。
Patrick M

22

2.1.xJacksonでSpring Boot を2.9使用している場合は、次のアプリケーションプロパティを使用できます。

spring.jackson.mapper.accept-case-insensitive-enums=true


3

GETパラメータの大文字と小文字を無視してEnumを逆シリアル化しようとする場合、ACCEPT_CASE_INSENSITIVE_ENUMSを有効にしても効果はありません。このオプションはボディの非シリアル化でのみ機能するため、役に立ちません。代わりにこれを試してください:

public class StringToEnumConverter implements Converter<String, Modes> {
    @Override
    public Modes convert(String from) {
        return Modes.valueOf(from.toUpperCase());
    }
}

その後

@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void addFormatters(FormatterRegistry registry) {
        registry.addConverter(new StringToEnumConverter());
    }
}

答えとコードサンプルはここからです


1

@Konstantin Zyubinへの謝罪で、彼の答えは私が必要とするものに近かった-しかし私はそれを理解していなかったので、ここにそれがどうあるべきかと思う:

1つの列挙型を大文字と小文字を区別せずに逆シリアル化する場合、つまり、アプリケーション全体の動作を変更したくない、または変更できない場合は、サブクラス化StdConverterして強制することにより、1つの型のみのカスタムデシリアライザーを作成できます。ジャクソンは、JsonDeserialize注釈を使用して関連するフィールドでのみ使用します。

例:

public class ColorHolder {

  public enum Color {
    RED, GREEN, BLUE
  }

  public static final class ColorParser extends StdConverter<String, Color> {
    @Override
    public Color convert(String value) {
      return Arrays.stream(Color.values())
        .filter(e -> e.getName().equalsIgnoreCase(value.trim()))
        .findFirst()
        .orElseThrow(() -> new IllegalArgumentException("Invalid value '" + value + "'"));
    }
  }

  @JsonDeserialize(converter = ColorParser.class)
  Color color;
}

0

問題はcom.fasterxml.jackson.databind.util.EnumResolverに関連しています。HashMapを使用して列挙値を保持し、HashMapは大文字と小文字を区別しないキーをサポートしません。

上記の回答では、すべての文字は大文字または小文字である必要があります。しかし、私はそれを持つ列挙型のすべての(非)センシティブな問題を修正しました:

https://gist.github.com/bhdrk/02307ba8066d26fa1537

CustomDeserializers.java

import com.fasterxml.jackson.databind.BeanDescription;
import com.fasterxml.jackson.databind.DeserializationConfig;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.deser.std.EnumDeserializer;
import com.fasterxml.jackson.databind.module.SimpleDeserializers;
import com.fasterxml.jackson.databind.util.EnumResolver;

import java.util.HashMap;
import java.util.Map;


public class CustomDeserializers extends SimpleDeserializers {

    @Override
    @SuppressWarnings("unchecked")
    public JsonDeserializer<?> findEnumDeserializer(Class<?> type, DeserializationConfig config, BeanDescription beanDesc) throws JsonMappingException {
        return createDeserializer((Class<Enum>) type);
    }

    private <T extends Enum<T>> JsonDeserializer<?> createDeserializer(Class<T> enumCls) {
        T[] enumValues = enumCls.getEnumConstants();
        HashMap<String, T> map = createEnumValuesMap(enumValues);
        return new EnumDeserializer(new EnumCaseInsensitiveResolver<T>(enumCls, enumValues, map));
    }

    private <T extends Enum<T>> HashMap<String, T> createEnumValuesMap(T[] enumValues) {
        HashMap<String, T> map = new HashMap<String, T>();
        // from last to first, so that in case of duplicate values, first wins
        for (int i = enumValues.length; --i >= 0; ) {
            T e = enumValues[i];
            map.put(e.toString(), e);
        }
        return map;
    }

    public static class EnumCaseInsensitiveResolver<T extends Enum<T>> extends EnumResolver<T> {
        protected EnumCaseInsensitiveResolver(Class<T> enumClass, T[] enums, HashMap<String, T> map) {
            super(enumClass, enums, map);
        }

        @Override
        public T findEnum(String key) {
            for (Map.Entry<String, T> entry : _enumsById.entrySet()) {
                if (entry.getKey().equalsIgnoreCase(key)) { // magic line <--
                    return entry.getValue();
                }
            }
            return null;
        }
    }
}

使用法:

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;


public class JSON {

    public static void main(String[] args) {
        SimpleModule enumModule = new SimpleModule();
        enumModule.setDeserializers(new CustomDeserializers());

        ObjectMapper mapper = new ObjectMapper();
        mapper.registerModule(enumModule);
    }

}

0

IagoFernándezとPaulのソリューションの修正版を使用しました。

大文字と小文字を区別する必要があるenumがrequestobjectにありました

@POST
public Response doSomePostAction(RequestObject object){
 //resource implementation
}



class RequestObject{
 //other params 
 MyEnumType myType;

 @JsonSetter
 public void setMyType(String type){
   myType = MyEnumType.valueOf(type.toUpperCase());
 }
 @JsonGetter
 public String getType(){
   return myType.toString();//this can change 
 }
}

0

ジャクソンで列挙型の大文字と小文字を区別しない逆シリアル化を許可するには、以下のプロパティをapplication.propertiesスプリングブートプロジェクトのファイルに追加するだけです。

spring.jackson.mapper.accept-case-insensitive-enums=true

プロパティファイルのyamlバージョンがある場合は、以下のプロパティをファイルに追加しapplication.ymlます。

spring:
  jackson:
    mapper:
      accept-case-insensitive-enums: true

-1

大文字と小文字を区別しない方法でデシリアライズしたい場合(質問に投稿されたコードに基づいて構築する)、列挙型を処理する方法を次に示します。

@JsonIgnore
public void setDataType(DataType dataType)
{
  type = dataType;
}

@JsonProperty
public void setDataType(String dataType)
{
  // Clean up/validate String however you want. I like
  // org.apache.commons.lang3.StringUtils.trimToEmpty
  String d = StringUtils.trimToEmpty(dataType).toUpperCase();
  setDataType(DataType.valueOf(d));
}

enumが自明ではなく、それ自体のクラスにある場合、通常は小文字の文字列を処理する静的解析メソッドを追加します。


-1

ジャクソンで列挙型をデシリアライズするのは簡単です。Stringに基づいて列挙型をデシリアライズする場合は、コンストラクター、列挙型へのゲッターおよびセッターが必要です。また、その列挙型を使用するクラスには、StringではなくDataTypeをパラメーターとして受け取るセッターが必要です。

public class Endpoint {

     public enum DataType {
        JSON("json"), HTML("html");

        private String type;

        @JsonValue
        public String getDataType(){
           return type;
        }

        @JsonSetter
        public void setDataType(String t){
           type = t.toLowerCase();
        }
     }

     public String url;
     public DataType type;

     public Endpoint() {

     }

     public void setType(DataType dataType){
        type = dataType;
     }

}

JSONがある場合、JacksonのObjectMapperを使用してEndpointクラスに逆シリアル化できます。

ObjectMapper mapper = new ObjectMapper();
mapper.enable(SerializationFeature.INDENT_OUTPUT);
try {
    Endpoint endpoint = mapper.readValue("{\"url\":\"foo\",\"type\":\"json\"}", Endpoint.class);
} catch (IOException e1) {
        // TODO Auto-generated catch block
    e1.printStackTrace();
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.