Jackson enum Serializing and DeSerializer


225

JAVA 1.6とJackson 1.9.9を使用しています。列挙型があります。

public enum Event {
    FORGOT_PASSWORD("forgot password");

    private final String value;

    private Event(final String description) {
        this.value = description;
    }

    @JsonValue
    final String value() {
        return this.value;
    }
}

@JsonValueを追加しました。これは、オブジェクトをシリアル化するジョブを実行するようです:

{"event":"forgot password"}

しかし、逆シリアル化しようとすると、

Caused by: org.codehaus.jackson.map.JsonMappingException: Can not construct instance of com.globalrelay.gas.appsjson.authportal.Event from String value 'forgot password': value not one of declared Enum instance names

ここで何が欠けていますか?


4
試しました{"Event":"FORGOT_PASSWORD"}か?イベントとFORGOT_PASSWORDの両方の上限に注意してください。
OldCurmudgeon 2012


ここに来た人は、異なる命名規則に従っている場合、つまり、getValueこれGetValueが機能しない場合は、getter setter構文も確認してください
DavutGürbüzApr

回答:


286

@xbakesxが指摘するシリアライザー /デシリアライザーソリューションは、JSON表現から列挙型クラスを完全に切り離したい場合に最適です。

または、自己完結型のソリューションを希望する場合は、@JsonCreatorおよび@JsonValueアノテーションに基づく実装の方が便利です。

したがって、@ Stanleyの例を利用すると、以下は完全な自己完結型ソリューションです(Java 6、Jackson 1.9)。

public enum DeviceScheduleFormat {

    Weekday,
    EvenOdd,
    Interval;

    private static Map<String, DeviceScheduleFormat> namesMap = new HashMap<String, DeviceScheduleFormat>(3);

    static {
        namesMap.put("weekday", Weekday);
        namesMap.put("even-odd", EvenOdd);
        namesMap.put("interval", Interval);
    }

    @JsonCreator
    public static DeviceScheduleFormat forValue(String value) {
        return namesMap.get(StringUtils.lowerCase(value));
    }

    @JsonValue
    public String toValue() {
        for (Entry<String, DeviceScheduleFormat> entry : namesMap.entrySet()) {
            if (entry.getValue() == this)
                return entry.getKey();
        }

        return null; // or fail
    }
}

@アグスティは私の質問を見てください、私はそこに何が足りないのですかstackoverflow.com/questions/30525986/enum-is-not-binding
Prabjot Singh

25
一部の人には明らかかもしれませんが、@ JsonValueはシリアライゼーションに使用され、@ JsonCreatorはデシリアライゼーションに使用されます。両方を行わない場合は、どちらか一方だけが必要になります。
acvcu

6
2つの真実の情報源を紹介するという単純な事実のため、私はこの解決策を本当に嫌っています。開発者は常に2つの場所に名前を追加することを忘れないでください。私は、enumの内部をマップで装飾することなく、正しいことを行うソリューションを好みます。
mttdbrd

2
@ttdbrd真実を統一するためにこれはどうですか?gist.github.com/Scuilion/036c53fd7fee2de89701a95822c0fb60
KevinO

1
静的マップの代わりに、YourEnum.values()を使用してYourEnumの配列を取得し、それを反復することができます
Valeriy K.

209

2015年6月のこのコミット(Jackson 2.6.2以降)では、次のように簡単に記述できることに注意してください。

public enum Event {
    @JsonProperty("forgot password")
    FORGOT_PASSWORD;
}

1
素晴らしい解決策。Dropwizardに2.6.0がバンドルされているのは残念です:-(
Clint Eastwood

1
残念ながら、これは列挙型を文字列に変換するときにプロパティを返しません。
ニコラス

4
この機能は2.8以降廃止されました。
pqian 2017年

2
このソリューションは、Enumのシリアライズとデシリアライズの両方で機能します。2.8でテスト済み。
ダウンヒル

1
:すべてで廃止されるようには見えませんgithub.com/FasterXML/jackson-annotations/blob/master/src/main/...
パブロ・

88

単一の引数を取り、注釈を付ける静的ファクトリメソッドを作成する必要があります@JsonCreator(Jackson 1.2以降で使用可能)

@JsonCreator
public static Event forValue(String value) { ... }

JsonCreatorアノテーションについて詳しくは、こちらをご覧ください


10
これは最もクリーンで最も簡潔なソリューションであり、残りは何トンものボイラープレートであり、すべてのコストで回避することができます(すべきです!)。
クリントイーストウッド

4
@JSONValueシリアライズおよび@JSONCreatorデシリアライズします。
チランジブ2017年

@JsonCreator public static Event valueOf(int intValue) { ... }列挙子に逆シリアル化intEventます。
Eido95 2018年

1
@ClintEastwood他のソリューションを回避すべきかどうかは、シリアル化/逆シリアル化の懸念を列挙型から分離するかどうかによって異なります。
Asa

44

実際の答え:

enumのデフォルトのデシリアライザーはデシリアライズに使用.name()するため、を使用していません@JsonValue。したがって、@ OldCurmudgeonが指摘したように{"event": "FORGOT_PASSWORD"}.name()値と一致するように渡す必要があります。

他のオプション(書き込みと読み取りのjson値を同じにしたい場合)...

より詳しい情報:

ジャクソンを使用してシリアライゼーションおよびデシリアライゼーションプロセスを管理する別の方法が(まだ)あります。これらのアノテーションを指定して、独自のカスタムシリアライザーとデシリアライザーを使用できます。

@JsonSerialize(using = MySerializer.class)
@JsonDeserialize(using = MyDeserializer.class)
public final class MyClass {
    ...
}

次に、あなたが書かなければならないMySerializerMyDeserializerされ、このようになります。

MySerializer

public final class MySerializer extends JsonSerializer<MyClass>
{
    @Override
    public void serialize(final MyClass yourClassHere, final JsonGenerator gen, final SerializerProvider serializer) throws IOException, JsonProcessingException
    {
        // here you'd write data to the stream with gen.write...() methods
    }

}

MyDeserializer

public final class MyDeserializer extends org.codehaus.jackson.map.JsonDeserializer<MyClass>
{
    @Override
    public MyClass deserialize(final JsonParser parser, final DeserializationContext context) throws IOException, JsonProcessingException
    {
        // then you'd do something like parser.getInt() or whatever to pull data off the parser
        return null;
    }

}

最後の少し、特にこれをJsonEnumメソッドgetYourValue()でシリアル化する列挙型に対して行う場合、シリアライザとデシリアライザは次のようになります。

public void serialize(final JsonEnum enumValue, final JsonGenerator gen, final SerializerProvider serializer) throws IOException, JsonProcessingException
{
    gen.writeString(enumValue.getYourValue());
}

public JsonEnum deserialize(final JsonParser parser, final DeserializationContext context) throws IOException, JsonProcessingException
{
    final String jsonValue = parser.getText();
    for (final JsonEnum enumValue : JsonEnum.values())
    {
        if (enumValue.getYourValue().equals(jsonValue))
        {
            return enumValue;
        }
    }
    return null;
}

3
カスタム(デ)シリアライザーの使用は単純さを損なう(ジャクソンを使用することは価値がある)ので、これは非常に重い状況で必要です。以下で説明するように、@ JsonCreatorを使用し、このコメント
Dmitry Gryazin 14年

1
この解決策は、OPの質問で紹介されたややクレイジーな問題に最適です。ここでの本当の問題は、OPが構造化データをレンダリングされた形式で返したいということです。つまり、ユーザーフレンドリーな文字列が既に含まれているデータを返します。ただし、レンダリングされたフォームを識別子に戻すには、変換を元に戻すことができるコードが必要です。ハッキーに受け入れられた回答は、変換を処理するためにマップを使用したいが、より多くのメンテナンスが必要です。このソリューションを使用すると、新しい列挙型を追加して、開発者が自分の仕事に取り掛かることができます。
mttdbrd 2017

34

私の場合のように列挙型クラスを変更できない場合に特に便利な、非常に優れた簡潔なソリューションを見つけました。次に、特定の機能を有効にしたカスタムObjectMapperを提供する必要があります。これらの機能は、Jackson 1.6以降で使用できます。したがってtoString()、列挙型にメソッドを記述するだけで済みます。

public class CustomObjectMapper extends ObjectMapper {
    @PostConstruct
    public void customConfiguration() {
        // Uses Enum.toString() for serialization of an Enum
        this.enable(WRITE_ENUMS_USING_TO_STRING);
        // Uses Enum.toString() for deserialization of an Enum
        this.enable(READ_ENUMS_USING_TO_STRING);
    }
}

利用可能な列挙型関連の機能が他にもあります。ここを参照してください:

https://github.com/FasterXML/jackson-databind/wiki/Serialization-Features https://github.com/FasterXML/jackson-databind/wiki/Deserialization-Features


10
クラスを拡張する必要がある理由がわかりません。ObjectMapperのインスタンスでこの機能を有効にできます。
mttdbrd 2016年

1彼は[READ | WRITE]に私を指摘ので、私は春のapplication.ymlに使用することができます_ENUMS_USING_TO_STRING
HelLViS69

8

これを試して。

public enum Event {

    FORGOT_PASSWORD("forgot password");

    private final String value;

    private Event(final String description) {
        this.value = description;
    }

    private Event() {
        this.value = this.name();
    }

    @JsonValue
    final String value() {
        return this.value;
    }
}

6

任意の属性の逆シリアル化をカスタマイズできます。

import com.fasterxml.jackson.databind.annotation.JsonDeserialize処理される属性のannotationJsonDeserialize()を使用して、デシリアライズクラスを宣言します。これが列挙型の場合:

@JsonDeserialize(using = MyEnumDeserialize.class)
private MyEnum myEnum;

このようにして、クラスを使用して属性をデシリアライズします。これは完全な例です:

public class MyEnumDeserialize extends JsonDeserializer<MyEnum> {

    @Override
    public MyEnum deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException {
        JsonNode node = jsonParser.getCodec().readTree(jsonParser);
        MyEnum type = null;
        try{
            if(node.get("attr") != null){
                type = MyEnum.get(Long.parseLong(node.get("attr").asText()));
                if (type != null) {
                    return type;
                }
            }
        }catch(Exception e){
            type = null;
        }
        return type;
    }
}

ナサニエル・フォード、良くなった?
Fernando Gomes

1
はい、これははるかに良い答えです。それはいくつかのコンテキストを提供します。しかし、私はさらに進んで、なぜこのように逆シリアル化を追加することがOPの特定の障害に対処するのかを議論します。
Nathaniel Ford

5

JSONオブジェクトを列挙型に逆シリアル化するには、さまざまな方法があります。私の好きなスタイルは内部クラスを作ることです:

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.hibernate.validator.constraints.NotEmpty;

import java.util.Arrays;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;

import static com.fasterxml.jackson.annotation.JsonFormat.Shape.OBJECT;

@JsonFormat(shape = OBJECT)
public enum FinancialAccountSubAccountType {
  MAIN("Main"),
  MAIN_DISCOUNT("Main Discount");

  private final static Map<String, FinancialAccountSubAccountType> ENUM_NAME_MAP;
  static {
    ENUM_NAME_MAP = Arrays.stream(FinancialAccountSubAccountType.values())
      .collect(Collectors.toMap(
        Enum::name,
        Function.identity()));
  }

  private final String displayName;

  FinancialAccountSubAccountType(String displayName) {
    this.displayName = displayName;
  }

  @JsonCreator
  public static FinancialAccountSubAccountType fromJson(Request request) {
    return ENUM_NAME_MAP.get(request.getCode());
  }

  @JsonProperty("name")
  public String getDisplayName() {
    return displayName;
  }

  private static class Request {
    @NotEmpty(message = "Financial account sub-account type code is required")
    private final String code;
    private final String displayName;

    @JsonCreator
    private Request(@JsonProperty("code") String code,
                    @JsonProperty("name") String displayName) {
      this.code = code;
      this.displayName = displayName;
    }

    public String getCode() {
      return code;
    }

    @JsonProperty("name")
    public String getDisplayName() {
      return displayName;
    }
  }
}

4

次に、マップの代わりに文字列値を使用する別の例を示します。

public enum Operator {
    EQUAL(new String[]{"=","==","==="}),
    NOT_EQUAL(new String[]{"!=","<>"}),
    LESS_THAN(new String[]{"<"}),
    LESS_THAN_EQUAL(new String[]{"<="}),
    GREATER_THAN(new String[]{">"}),
    GREATER_THAN_EQUAL(new String[]{">="}),
    EXISTS(new String[]{"not null", "exists"}),
    NOT_EXISTS(new String[]{"is null", "not exists"}),
    MATCH(new String[]{"match"});

    private String[] value;

    Operator(String[] value) {
        this.value = value;
    }

    @JsonValue
    public String toStringOperator(){
        return value[0];
    }

    @JsonCreator
    public static Operator fromStringOperator(String stringOperator) {
        if(stringOperator != null) {
            for(Operator operator : Operator.values()) {
                for(String operatorString : operator.value) {
                    if (stringOperator.equalsIgnoreCase(operatorString)) {
                        return operator;
                    }
                }
            }
        }
        return null;
    }
}

4

列挙型のコンテキストでは、@JsonValuenow(2.0以降)を使用すると、シリアライゼーションデシリアライゼーションが機能します。

jackson-annotations javadocに@JsonValueよると:

注:Java列挙型で使用する場合のもう1つの機能は、注釈付きメソッドから返される値も、シリアル化するJSON文字列だけでなく、逆シリアル化する値と見なされることです。Enum値のセットは定数であり、マッピングを定義することは可能であるため、これは可能ですが、POJOタイプでは一般的に行うことができません。そのため、これはPOJOの逆シリアル化には使用されません。

したがってEvent、上記のように列挙型に注釈を付けると、Jackson 2.0+で(シリアライゼーションとデシリアライゼーションの両方で)機能します。


3

@JsonSerialize @JsonDeserializeを使用する以外に、オブジェクトマッパーでSerializationFeatureおよびDeserializationFeature(jacksonバインディング)を使用することもできます。

DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_USING_DEFAULT_VALUEなど。指定された列挙型が列挙型クラスで定義されていない場合にデフォルトの列挙型を提供します。


0

私が見つけた最も簡単な方法は、列挙型に@ JsonFormat.Shape.OBJECTアノテーションを使用することです。

@JsonFormat(shape = JsonFormat.Shape.OBJECT)
public enum MyEnum{
    ....
}

0

私の場合、これは解決されたものです:

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonProperty;

@JsonFormat(shape = JsonFormat.Shape.OBJECT)
public enum PeriodEnum {

    DAILY(1),
    WEEKLY(2),
    ;

    private final int id;

    PeriodEnum(int id) {
        this.id = id;
    }

    public int getId() {
        return id;
    }

    public String getName() {
        return this.name();
    }

    @JsonCreator
    public static PeriodEnum fromJson(@JsonProperty("name") String name) {
        return valueOf(name);
    }
}

次のjsonをシリアライズおよびデシリアライズします。

{
  "id": 2,
  "name": "WEEKLY"
}

お役に立てば幸いです。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.