2012-09-18 22 views
128

używam Java 1.6 i Jackson 1.9.9 Mam enumJackson enum szeregowania i Deserializator

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

Dodałem @JsonValue, to wydaje się, aby wykonać zadanie to serializuje obiektu do:

{"event":"forgot password"} 

ale gdy próbuję deserializowania dostaję

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 

Co ja tu brakuje?

+2

Czy próbowałeś '{" Event ":" FORGOT_PASSWORD "}'? Zwróć uwagę na czapki w obu Zdarzeniach i FORGOT_PASSWORD. – OldCurmudgeon

+0

Podobne: [Jak nanieść adnotację na pola wyliczeniowe do deserializacji za pomocą Jackson json] (https://stackoverflow.com/questions/9300191/how-to-annotate-enum-fields-for-deserialization-using-jackson-json) – Vadzim

Odpowiedz

167

Rozwiązanie serializera/deserializera wskazane przez xbakesx jest znakomite, jeśli chcemy całkowicie oddzielić klasę yor enum od jej reprezentacji JSON.

Ewentualnie, jeśli preferujesz samodzielne rozwiązanie, wygodniejsze będzie wdrożenie oparte na adnotacjach @JsonCreator i @JsonValue.

Oto przykład zastosowania Stanleya jako kompletnego samodzielnego rozwiązania (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 
    } 
} 
+6

Dodałem tylko @JsonValue dla mnie. Dzięki. – Alessandro

+0

@Agusti proszę spojrzeć na moje pytanie, co mam tam brakuje http://stackoverflow.com/questions/30525986/enum-is-not-binding –

+8

może być oczywiste dla niektórych, ale zauważ, że @ JsonValue służy do serializacji i @ JsonCreator do deserializacji. Jeśli nie robisz obu, potrzebujesz tylko jednego lub drugiego. – acvcu

35

Rzeczywista Odpowiedź:

Domyślna Deserializator za teksty stałe korzysta .name() deserializacji, więc to nie jest pomocą @JsonValue. Tak jak zauważył @OldCurmudgeon, musisz podać {"event": "FORGOT_PASSWORD"}, aby dopasować wartość .name().

Innym rozwiązaniem (zakładając, że chcesz pisać i czytać wartości json być takie same) ...

Więcej informacji:

Nie ma (jeszcze) inny sposób zarządzać i serializacji proces deserializacji z Jacksonem. Można określić te adnotacje używać swój własny serializatora i Deserializator:

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

Następnie trzeba napisać MySerializer i MyDeserializer które wyglądać tak:

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

} 

Ostatnio trochę, szczególnie dla tej operacji do wyliczenia JsonEnum że serializes metodą getYourValue(), Twój serializer i Deserializator może wyglądać następująco:

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

The użycie niestandardowego (de) serializera zabija prostotę (z której korzysta Jackson, btw), więc jest to potrzebne w naprawdę ciężkich sytuacjach. Użyj @JsonCreator, jak opisano poniżej, i sprawdź [ten komentarz] (http://jira.codehaus.org/browse/JACKSON-861) –

+1

To rozwiązanie jest najlepsze dla nieco szalonego problemu wprowadzonego w pytaniu OP. Prawdziwym problemem jest to, że OP chce zwrócić uporządkowane dane w postaci * renderowanej *. Oznacza to, że zwracają dane, które zawierają już łańcuch przyjazny dla użytkownika. Ale aby przekształcić renderowaną formę z powrotem w identyfikator, potrzebujemy kodu, który może odwrócić transformację. Przyjęta przez hakerów odpowiedź chce użyć mapy do obsługi transformacji, ale wymaga więcej konserwacji. Dzięki temu rozwiązaniu można dodawać nowe typy wyliczeniowe, a następnie programiści mogą wykonywać swoje zadania. – mttdbrd

68

Należy utworzyć statyczną metodę fabryczną która przyjmuje jeden argument i opisywanie go @JsonCreator (dostępny od Jackson 1.2)

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

więcej o JsonCreator adnotacji here.

+8

Jest to najczystsze i najbardziej zwięzłe rozwiązanie, reszta to tylko tony elementów, które można (i należy!) Za wszelką cenę uniknąć! –

+1

'@ JSONValue' do serializowania i' @ JSONCreator' do deserializacji. – Chiranjib

23

Znalazłem bardzo ładne i zwięzłe rozwiązanie, szczególnie przydatne, gdy nie można modyfikować klas wyliczeniowych, tak jak było w moim przypadku. Następnie powinieneś udostępnić niestandardową ObjectMapper z włączoną określoną funkcją. Te funkcje są dostępne od Jackson 1.6. Musisz więc tylko zapisać w swoim wyliczeniu metodę 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); 
    } 
} 

Istnieje więcej funkcji związanych z enum-dostępne, zobacz tutaj:

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

+3

nie wiesz, dlaczego musisz przedłużyć klasę. możesz włączyć tę funkcję na instancji ObjectMapper. – mttdbrd

+0

+1, ponieważ wskazał mi [READ | WRITE] _ENUMS_USING_TO_STRING, którego mogę użyć na wiosnę application.yml – HelLViS69

+0

Linki do wiki.fasterxml.com przestały działać –

111

uwaga, że ​​od this commit w czerwcu 2015 (Jackson 2.6.2 i wyżej) teraz po prostu może napisz:

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

czas na aktualizację. –

+1

fajne rozwiązanie. Szkoda, że ​​utknąłem z pakietem 2.6.0 w Dropwizard :-( –

+0

To będzie tylko serializować, a nie deserializować. –

1

Możesz dostosować deserializację dla dowolnego atrybutu.

Deklaracja deserializacji klasy za pomocą adnotacjiJsonDeserialize (import com.fasterxml.jackson.databind.annotation.JsonDeserialize) dla atrybutu, który będzie przetwarzany. Jeśli to jest Enum:

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

W ten sposób klasa będzie używana do deserializacji atrybutu. To jest pełny przykład:

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

należy wyjaśnić, * dlaczego * to działa –

+0

Nathaniel Ford, jeszcze lepszy –

+1

Tak, jest to o wiele lepsze rozwiązanie;..? to zapewnia pewien kontekst Chciałbym pójść jeszcze dalej, choć i dyskutować. dlaczego dodanie deserializacji w ten sposób rozwiązuje specyficzną przeszkodę PO. –

3

Oto inny przykład, który używa wartości ciągów zamiast mapy.

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

Istnieje wiele metod, które można zastosować do przeprowadzenia deserializacji obiektu JSON do wyliczenia. Moim ulubionym stylem jest stworzenie klasy wewnętrznej:

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