Jackson enum Serializing dan DeSerializer

225

Saya menggunakan JAVA 1.6 dan Jackson 1.9.9 Saya mendapat enum

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;
    }
}

Saya telah menambahkan @JsonValue, ini tampaknya melakukan pekerjaan yang membuat serialisasi objek menjadi:

{"event":"forgot password"}

tetapi ketika saya mencoba deserialize saya mendapatkan

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

Apa yang kulewatkan di sini?

pookieman
sumber
4
Sudahkah Anda mencoba {"Event":"FORGOT_PASSWORD"}? Perhatikan batas pada Event dan FORGOT_PASSWORD.
OldCurmudgeon
Siapa yang datang ke sini juga memeriksa sintaks pengambil getter jika Anda mengikuti konvensi penamaan yang berbeda yaitu bukannya getValueini GetValuetidak bekerja
Davut Gürbüz

Jawaban:

287

Solusi serializer / deserializer yang ditunjukkan oleh @xbakesx adalah solusi yang sangat baik jika Anda ingin memisahkan kelas enum Anda dari representasi JSON.

Atau, jika Anda lebih suka solusi mandiri, implementasi berdasarkan @JsonCreatordan @JsonValueanotasi akan lebih nyaman.

Jadi memanfaatkan contoh oleh @Stanley berikut ini adalah solusi lengkap mandiri (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
    }
}
Agustí Sánchez
sumber
@Agusti silakan lihat pertanyaan saya, apa yang saya lewatkan di sana stackoverflow.com/questions/30525986/enum-is-not-binding
Prabjot Singh
25
mungkin jelas bagi sebagian orang, tetapi perhatikan bahwa @ JsonValue digunakan untuk serialisasi dan @ JsonCreator untuk deserialisasi. Jika Anda tidak melakukan keduanya, Anda hanya perlu satu atau yang lain.
acvcu
6
Saya benar-benar tidak menyukai solusi ini karena Anda memperkenalkan dua sumber kebenaran. Pengembang harus selalu ingat untuk menambahkan nama di dua tempat. Saya lebih suka solusi yang hanya melakukan hal yang benar tanpa mendekorasi internal enum dengan peta.
mttdbrd
2
@ ttdbrd bagaimana dengan ini untuk menyatukan kebenaran? gist.github.com/Scuilion/036c53fd7fee2de89701a95822c0fb60
KevinO
1
Alih-alih peta statis, Anda dapat menggunakan YourEnum.values ​​() yang memberikan Array of YourEnum dan beralih di atasnya
Valeriy K.
209

Perhatikan bahwa pada komit ini pada Juni 2015 (Jackson 2.6.2 dan lebih tinggi) Anda sekarang dapat menulis:

public enum Event {
    @JsonProperty("forgot password")
    FORGOT_PASSWORD;
}
tif
sumber
1
solusi yang bagus. Sayang sekali saya terjebak dengan 2.6.0 dibundel dalam Dropwizard :-(
Clint Eastwood
1
Sayangnya ini tidak mengembalikan properti saat mengonversi enum Anda menjadi sebuah string.
Nicholas
4
Fitur ini sudah tidak digunakan lagi sejak 2.8.
pqian
2
Solusi ini berfungsi untuk serialisasi dan deserialize di Enum. Diuji dalam 2.8.
Downhillski
1
Tampaknya tidak ditinggalkan sama sekali: github.com/FasterXML/jackson-annotations/blob/master/src/main/…
pablo
88

Anda harus membuat metode pabrik statis yang mengambil argumen tunggal dan menjelaskannya dengan @JsonCreator(tersedia sejak Jackson 1.2)

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

Baca lebih lanjut tentang anotasi JsonCreator di sini .

Stanley
sumber
10
Ini adalah solusi paling bersih dan paling ringkas, sisanya hanya ton boilerplate yang bisa (dan harus!) Dihindari dengan cara apa pun!
Clint Eastwood
4
@JSONValueuntuk membuat cerita bersambung dan @JSONCreatordeserialize.
Chiranjib
@JsonCreator public static Event valueOf(int intValue) { ... }deserialize intke Eventenumerator.
Eido95
1
@ClintEastwood apakah solusi lain harus dihindari tergantung pada apakah Anda ingin memisahkan masalah serialisasi / deserialisasi dari enum atau tidak.
Asa
44

Jawaban Aktual:

Deserializer default untuk enums digunakan .name()untuk deserialize, jadi itu tidak menggunakan @JsonValue. Jadi seperti yang ditunjukkan oleh @OldCurmudgeon, Anda harus masuk {"event": "FORGOT_PASSWORD"}untuk mencocokkan .name()nilainya.

Opsi lain (dengan asumsi Anda ingin nilai tulis dan baca json sama) ...

Info lebih lanjut:

Ada (belum) cara lain untuk mengelola proses serialisasi dan deserialisasi dengan Jackson. Anda dapat menentukan anotasi ini untuk menggunakan serializer dan deserializer kustom Anda sendiri:

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

Maka Anda harus menulis MySerializerdan MyDeserializeryang terlihat seperti ini:

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;
    }

}

Sedikit terakhir, terutama untuk melakukan ini pada enum JsonEnumyang membuat serial dengan metode ini getYourValue(), serializer dan deserializer Anda mungkin terlihat seperti ini:

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;
}
xbakesx
sumber
3
Penggunaan serializer kustom (de) membunuh kesederhanaan (yang menggunakan Jackson bernilai, btw), jadi ini diperlukan dalam situasi yang sangat berat. Gunakan @JsonCreator, seperti dijelaskan di bawah ini, dan periksa komentar ini
Dmitry Gryazin
1
Soluiton ini adalah yang terbaik untuk masalah yang agak gila yang diperkenalkan dalam pertanyaan OPs. Masalah sebenarnya di sini adalah bahwa OP ingin mengembalikan data terstruktur dalam bentuk yang diberikan . Artinya, mereka mengembalikan data yang sudah menyertakan string ramah pengguna. Tetapi untuk mengubah formulir yang dirender kembali menjadi pengenal, kita memerlukan beberapa kode yang dapat membalikkan transformasi. Jawaban yang diterima yang meretas ingin menggunakan peta untuk menangani transformasi, tetapi membutuhkan lebih banyak perawatan. Dengan solusi ini, Anda dapat menambahkan tipe enumerasi baru dan kemudian pengembang Anda dapat melanjutkan pekerjaan mereka.
mttdbrd
34

Saya telah menemukan solusi yang sangat bagus dan ringkas, terutama berguna ketika Anda tidak dapat memodifikasi kelas enum seperti dalam kasus saya. Maka Anda harus menyediakan ObjectMapper kustom dengan fitur tertentu yang diaktifkan. Fitur-fitur tersebut tersedia sejak Jackson 1.6. Jadi Anda hanya perlu menulis toString()metode di enum Anda.

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);
    }
}

Ada lebih banyak fitur terkait enum yang tersedia, lihat di sini:

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

lagivan
sumber
10
tidak yakin mengapa Anda perlu memperpanjang kelas. Anda dapat mengaktifkan fitur ini pada instance ObjectMapper.
mttdbrd
+1 karena dia mengarahkan saya ke [READ | WRITE] _ENUMS_USING_TO_STRING yang dapat saya gunakan di Spring application.yml
HelLViS69
8

Coba ini.

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;
    }
}
Durga
sumber
6

Anda dapat menyesuaikan deserialization untuk atribut apa pun.

Deklarasikan kelas deserialize Anda menggunakan annotationJsonDeserialize ( import com.fasterxml.jackson.databind.annotation.JsonDeserialize) untuk atribut yang akan diproses. Jika ini adalah Enum:

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

Dengan cara ini kelas Anda akan digunakan untuk menghapus atribut tersebut. Ini adalah contoh lengkap:

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
sumber
Nathaniel Ford, membaik?
Fernando Gomes
1
Ya, ini adalah jawaban yang jauh lebih baik; ini memberikan beberapa konteks. Saya akan melangkah lebih jauh, dan membahas mengapa menambahkan deserialisasi dengan cara ini mengatasi hambatan khusus OP.
Nathaniel Ford
5

Ada berbagai pendekatan yang dapat Anda lakukan untuk melakukan deserialisasi objek JSON ke enum. Gaya favorit saya adalah membuat kelas dalam:

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;
    }
  }
}
Sam Berry
sumber
4

Berikut adalah contoh lain yang menggunakan nilai string, bukan peta.

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;
    }
}
Gremash
sumber
4

Dalam konteks enum, menggunakan @JsonValuesekarang (sejak 2.0) berfungsi untuk serialisasi dan deserialisasi.

Menurut javadoc jackson-annotations untuk@JsonValue :

CATATAN: ketika digunakan untuk enum Java, satu fitur tambahan adalah bahwa nilai yang dikembalikan oleh metode beranotasi juga dianggap sebagai nilai deserialize dari, bukan hanya JSON String untuk diserialisasi sebagai. Ini dimungkinkan karena set nilai Enum adalah konstan dan dimungkinkan untuk mendefinisikan pemetaan, tetapi tidak dapat dilakukan secara umum untuk tipe POJO; dengan demikian, ini tidak digunakan untuk deserialisasi POJO.

Jadi memiliki Eventenum yang dianotasi sama seperti karya di atas (untuk serialisasi dan deserialisasi) dengan jackson 2.0+.

Brice Roncace
sumber
3

Selain menggunakan @JsonSerialize @JsonDeserialize, Anda juga dapat menggunakan SerializationFeature dan DeserializationFeature (pengikat jackson) di mapper objek.

Seperti DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_USING_DEFAULT_VALUE, yang memberikan tipe enum default jika yang disediakan tidak didefinisikan dalam kelas enum.

Yuantao
sumber
0

Cara paling sederhana yang saya temukan adalah menggunakan anotasi @ JsonFormat.Shape.OBJECT untuk enum.

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

Dalam kasus saya, inilah yang diselesaikan:

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);
    }
}

Serialize dan deserializes json berikut:

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

Saya harap ini membantu!

Flavio Sousa
sumber