Jackson enum Serializing ve DeSerializer


225

JAVA 1.6 ve Jackson 1.9.9 kullanıyorum.

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

Bir @JsonValue ekledim, bu nesneyi serileştiren işi yapıyor gibi görünüyor:

{"event":"forgot password"}

ama serileştirmeyi denediğimde

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

Burada ne eksik?


4
Denedin {"Event":"FORGOT_PASSWORD"}mi Hem Etkinlik hem de FORGOT_PASSWORD başlıklarına dikkat edin.
OldCurmudgeon


Kim, yani yerine farklı bir adlandırma kuralı takip ederseniz de alıcı ayarlayıcı sözdizimini kontrol buraya geldi getValuebu GetValueişi yapmaz
Davut Gürbüz

Yanıtlar:


288

Enum sınıfınızı JSON temsilinden tamamen ayırmak istiyorsanız @xbakesx tarafından belirtilen serileştirici / serileştirici çözüm mükemmel bir çözümdür .

Alternatif olarak, bağımsız bir çözümü tercih ederseniz, ek açıklamalara @JsonCreatorve @JsonValueek açıklamalara dayalı bir uygulama daha uygun olacaktır.

Bu yüzden @Stanley tarafından örnekten yararlanarak aşağıdakiler eksiksiz bir bağımsız çözümdür (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
    }
}

@Agusti lütfen soruma bir göz atın, orada neyi özlüyorum stackoverflow.com/questions/30525986/enum-is-not-binding
Prabjot Singh

25
belki bazıları için açıktır, ancak serileştirme için @ JsonValue ve serileştirme için @ JsonCreator kullanıldığını unutmayın. Her ikisini de yapmıyorsanız, sadece birine veya diğerine ihtiyacınız olacak.
acvcu

6
Bu gerçeği, iki hakikat kaynağını tanıttığınız için sevmiyorum. Geliştiricinin her zaman adı iki yere eklemeyi hatırlaması gerekir. Bir enumun içini harita ile süslemeden doğru olanı yapan bir çözümü tercih ederim.
mttdbrd

2
@ttdbrd gerçeği birleştirmek için buna ne dersiniz? gist.github.com/Scuilion/036c53fd7fee2de89701a95822c0fb60
KevinO

1
Statik harita yerine, YourEnum Dizisi veren ve üzerinde yineleme yapan YourEnum.values ​​() öğesini kullanabilirsiniz
Valeriy K.

210

Not itibariyle o bu taahhüt Haziran 2015 (Jackson 2.6.2 ve üzeri) içinde artık basitçe yazabilirsiniz:

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

1
güzel bir çözüm. Dropwizard'da paketlenmiş 2.6.0 ile takıldığım için bir utanç :-(
Clint Eastwood

1
Ne yazık ki bu, enum'unuzu bir dizeye dönüştürürken özelliği döndürmez.
Nicholas

4
Bu özellik 2.8'den beri kullanımdan kaldırıldı.
pqian

2
Bu çözüm Enum'da hem serileştirmek hem de serileştirmek için çalışır. 2.8'de test edilmiştir.
Downhillski

1
Hiç itiraz
pablo

89

Tek bir argüman alan statik bir fabrika yöntemi oluşturmanız ve bu açıklamaya ek açıklama eklemeniz gerekir @JsonCreator(Jackson 1.2'den beri kullanılabilir)

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

JsonCreator ek açıklaması hakkında daha fazla bilgiyi buradan edinebilirsiniz .


10
Bu en temiz ve en özlü çözümdür, geri kalanı her ne pahasına olursa olsun önlenebilecek tonlarca kazan plakasıdır!
Clint Eastwood

4
@JSONValueserileştirmek ve @JSONCreatorserileştirmek.
Chiranjib

@JsonCreator public static Event valueOf(int intValue) { ... }serisini intiçin Eventnumaralayıcıya.
Eido95

1
@ClintEastwood, diğer çözümlerden kaçınılması gerekip gerekmediği, serialzation / deserialization endişelerini numaralandırmadan ayırmak isteyip istemediğinize bağlıdır.
Asa

44

Gerçek Cevap:

Numaralandırmalar için varsayılan deserializer .name(), serisini kaldırmak için kullanır , bu nedenle kullanmaz @JsonValue. @OldCurmudgeon'un işaret ettiği gibi {"event": "FORGOT_PASSWORD"}, .name()değeri eşleştirmek için geçmeniz gerekir .

Diğer bir seçenek (yazma ve okuma json değerlerinin aynı olmasını istediğinizi varsayarsak) ...

Daha fazla bilgi:

Jackson ile serileştirme ve serileştirme sürecini yönetmenin başka bir yolu daha var. Kendi özel serileştiricinizi ve serileştiriciyi kullanmak için bu ek açıklamaları belirtebilirsiniz:

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

Sonra yazmak zorunda MySerializerve MyDeserializerhangisi şöyle:

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

}

Son olarak, özellikle bunu JsonEnumyöntemle seri hale gelen bir numaralandırmaya yapmak için getYourValue(), serileştiriciniz ve serileştiriciniz aşağıdaki gibi görünebilir:

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
Özel (de) serileştiricinin kullanımı basitliği öldürür (Jackson'ı kullanan btw için değerlidir), bu yüzden bu gerçekten ağır durumlarda gereklidir. Aşağıda açıklandığı gibi @JsonCreator kullanın ve bu yorumu
Dmitry Gryazin

1
Bu çözüm OP sorusunda ortaya çıkan biraz çılgın sorun için en iyisidir. Buradaki asıl sorun OP'nin yapılandırılmış verileri işlenmiş bir biçimde döndürmek istemesidir . Yani, kullanıcı dostu bir dize içeren verileri döndürüyorlar. Ancak, oluşturulan formu tekrar tanımlayıcıya dönüştürmek için dönüşümü tersine çevirebilecek bir koda ihtiyacımız var. Hacky kabul edilen cevap, dönüşümü ele almak için bir harita kullanmak ister, ancak daha fazla bakım gerektirir. Bu çözümle, yeni numaralandırılmış türler ekleyebilir ve ardından geliştiricileriniz işlerine devam edebilirler.
mttdbrd

34

Özellikle benim durumumda olduğu gibi enum sınıflarını değiştiremediğinizde çok güzel ve özlü bir çözüm buldum. Ardından, belirli bir özellik etkinleştirilmiş olan özel bir ObjectMapper sağlamalısınız. Bu özellikler Jackson 1.6'dan beri mevcuttur. Bu yüzden sadece toString()numaralamanıza yöntem yazmanız gerekir .

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

Numaralandırma ile ilgili daha fazla özellik var, buraya bakın:

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


10
sınıfı neden genişletmeniz gerektiğinden emin değilim. bu özelliği bir ObjectMapper örneğinde etkinleştirebilirsiniz.
mttdbrd

+1 çünkü beni Bahar uygulamasında kullanabileceğim [READ | WRITE] _ENUMS_USING_TO_STRING'i işaret etti.
HelLViS69

8

Bunu dene.

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

Diziselleştirmeyi herhangi bir özellik için özelleştirebilirsiniz.

import com.fasterxml.jackson.databind.annotation.JsonDeserializeİşlenecek öznitelik için annotationJsonDeserialize ( ) öğesini kullanarak serileştirme sınıfınızı bildirin . Bu bir Enum ise:

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

Bu şekilde, sınıfınız özelliğin serisini kaldırmak için kullanılacaktır. Bu tam bir örnektir:

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

Nathaniel Ford, daha iyi mi?
Fernando Gomes

1
Evet, bu çok daha iyi bir cevap; bir bağlam sağlar. Yine de daha ileri gideceğim ve bu şekilde serileştirme eklemenin neden OP'nin kendine özgü engeline değindiğini tartışacağım.
Nathaniel Ford

5

Bir JSON nesnesinin bir enum için serisini kaldırma işlemini gerçekleştirmek için kullanabileceğiniz çeşitli yaklaşımlar vardır. En sevdiğim tarz bir iç sınıf yapmak:

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

Harita yerine dize değerleri kullanan başka bir örnek.

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

Bir numaralandırma bağlamında, @JsonValueşimdi (2.0'dan beri) kullanmak serileştirme ve serileştirme için çalışır .

Göre için jackson-ek açıklamaları javadoc@JsonValue :

NOT: Java numaralandırmalarında kullanıldığında, ek bir özellik, açıklamalı yöntemle döndürülen değerin, sadece serileştirilecek JSON String değil, seriden çıkartılacak değer olarak kabul edilmesidir. Enum değerleri kümesi sabit olduğundan ve eşlemeyi tanımlamak mümkün olduğundan, ancak POJO türleri için genel olarak yapılamadığından bu mümkündür; bu nedenle, bu POJO serileştirmesi için kullanılmaz.

Bu yüzden Eventnumaralandırmanın yukarıdaki gibi açıklanması (hem serileştirme hem de serileştirme için) jackson 2.0+ ile çalışır.


3

@JsonSerialize @JsonDeserialize kullanmanın yanı sıra, nesne eşleştiricide SerializationFeature ve DeserializationFeature (jackson bağlama) da kullanabilirsiniz.

DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_USING_DEFAULT_VALUE gibi, sağlanan numaralandırma sınıfında tanımlanmamışsa varsayılan numaralandırma türü verir.


0

Bulduğum en basit yol numaralandırma için @ JsonFormat.Shape.OBJECT ek açıklamasını kullanmaktır.

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

0

Benim durumumda, bu çözüldü:

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

Aşağıdaki json'un serileştirmesini ve serisini kaldırır:

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

Umut ediyorum bu yardım eder!

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.