Jackson enum Serializing und DeSerializer

225

Ich benutze JAVA 1.6 und Jackson 1.9.9. Ich habe eine Aufzählung

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

Ich habe einen @JsonValue hinzugefügt. Dies scheint den Job zu erledigen, in den das Objekt serialisiert wird:

{"event":"forgot password"}

aber wenn ich versuche zu deserialisieren, bekomme ich eine

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

Was fehlt mir hier?

Pookieman
quelle
4
Hast du es versucht {"Event":"FORGOT_PASSWORD"}? Beachten Sie die Obergrenzen für Event und FORGOT_PASSWORD.
OldCurmudgeon
Wer kam hier auch Getter Setter - Syntax , wenn Sie andere Namenskonvention folgen also statt getValuedies GetValuenicht funktioniert
Davut Gürbüz

Antworten:

286

Die von @xbakesx aufgeführte Serializer / Deserializer-Lösung eignet sich hervorragend, wenn Sie Ihre Enum- Klasse vollständig von ihrer JSON-Darstellung entkoppeln möchten .

Wenn Sie eine in sich geschlossene Lösung bevorzugen, ist alternativ eine Implementierung basierend auf @JsonCreatorund @JsonValueAnmerkungen bequemer.

Wenn Sie also das Beispiel von @Stanley nutzen, ist Folgendes eine vollständige, in sich geschlossene Lösung (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
quelle
@Agusti bitte werfen Sie einen Blick auf meine Frage, was fehlt mir
Prabjot Singh
25
Für einige vielleicht offensichtlich, aber beachten Sie, dass @ JsonValue für die Serialisierung und @ JsonCreator für die Deserialisierung verwendet wird. Wenn Sie nicht beides tun, brauchen Sie nur das eine oder andere.
Acvcu
6
Ich mag diese Lösung wirklich nicht, weil Sie einfach zwei Quellen der Wahrheit einführen. Der Entwickler muss immer daran denken, den Namen an zwei Stellen hinzuzufügen. Ich bevorzuge eine Lösung, die genau das Richtige tut, ohne die Einbauten einer Aufzählung mit einer Karte zu dekorieren.
mttdbrd
2
@ttdbrd wie wäre es damit, Wahrheiten zu vereinen? gist.github.com/Scuilion/036c53fd7fee2de89701a95822c0fb60
KevinO
1
Anstelle einer statischen Karte können Sie YourEnum.values ​​() verwenden, die Array of YourEnum angeben und darauf iterieren
Valeriy K.
209

Beachten Sie, dass Sie ab diesem Commit im Juni 2015 (Jackson 2.6.2 und höher) einfach schreiben können:

public enum Event {
    @JsonProperty("forgot password")
    FORGOT_PASSWORD;
}
tif
quelle
1
schöne Lösung. Es ist eine Schande, dass ich mit 2.6.0 in Dropwizard gebündelt bin :-(
Clint Eastwood
1
Leider gibt dies die Eigenschaft nicht zurück, wenn Sie Ihre Aufzählung in eine Zeichenfolge konvertieren.
Nicholas
4
Diese Funktion ist seit 2.8 veraltet.
pqian
2
Diese Lösung funktioniert sowohl für die Serialisierung als auch für die Deserialisierung in Enum. Getestet in 2.8.
Downhillski
1
Scheint
88

Sie sollten eine statische Factory-Methode erstellen, die ein einzelnes Argument verwendet und mit Anmerkungen versehen @JsonCreator(verfügbar seit Jackson 1.2).

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

Lesen Sie mehr über JsonCreator Anmerkung hier .

Stanley
quelle
10
Dies ist die sauberste und prägnanteste Lösung, der Rest sind nur Tonnen von Boilerplate, die um jeden Preis vermieden werden könnten (und sollten!)!
Clint Eastwood
4
@JSONValuezu serialisieren und @JSONCreatorzu deserialisieren.
Chiranjib
@JsonCreator public static Event valueOf(int intValue) { ... }intzum EventEnumerator deserialisieren .
Eido95
1
@ClintEastwood Ob die anderen Lösungen vermieden werden sollten, hängt davon ab, ob Sie Serialisierungs- / Deserialisierungsprobleme von der Aufzählung trennen möchten oder nicht.
Asa
44

Aktuelle Antwort:

Der Standard-Deserializer für Aufzählungen wird .name()zum Deserialisieren verwendet, daher wird der nicht verwendet @JsonValue. Wie @OldCurmudgeon hervorhob, müssten Sie übergeben {"event": "FORGOT_PASSWORD"}, um dem .name()Wert zu entsprechen.

Eine andere Option (vorausgesetzt, Sie möchten, dass die json-Werte für Schreiben und Lesen gleich sind) ...

Mehr Info:

Es gibt (noch) eine andere Möglichkeit, den Serialisierungs- und Deserialisierungsprozess mit Jackson zu verwalten. Sie können diese Anmerkungen angeben, um Ihren eigenen benutzerdefinierten Serializer und Deserializer zu verwenden:

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

Dann muss man schreiben MySerializerund MyDeserializerwelche so aussehen:

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

}

Das letzte bisschen, insbesondere um dies mit einer Aufzählung zu tun JsonEnum, die mit der Methode getYourValue()serialisiert wird, könnte Ihr Serializer und Deserializer folgendermaßen aussehen:

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
quelle
3
Die Verwendung eines benutzerdefinierten (De-) Serialisierers macht die Einfachheit zunichte (die Verwendung von Jackson ist übrigens wert), so dass dies in sehr schweren Situationen erforderlich ist. Verwenden Sie @JsonCreator, wie unten beschrieben, und überprüfen Sie diesen Kommentar
Dmitry Gryazin
1
Diese Lösung eignet sich am besten für das etwas verrückte Problem, das in der OP-Frage eingeführt wurde. Das eigentliche Problem hierbei ist, dass das OP die strukturierten Daten in gerenderter Form zurückgeben möchte . Das heißt, sie geben Daten zurück, die bereits eine benutzerfreundliche Zeichenfolge enthalten. Um das gerenderte Formular wieder in einen Bezeichner umzuwandeln, benötigen wir Code, der die Transformation umkehren kann. Die von Hacky akzeptierte Antwort möchte eine Karte verwenden, um die Transformation durchzuführen, erfordert jedoch mehr Wartung. Mit dieser Lösung können Sie neue Aufzählungstypen hinzufügen und Ihre Entwickler können dann mit ihren Jobs fortfahren.
mttdbrd
34

Ich habe eine sehr schöne und prägnante Lösung gefunden, die besonders nützlich ist, wenn Sie die Enum-Klassen nicht wie in meinem Fall ändern können. Anschließend sollten Sie einen benutzerdefinierten ObjectMapper mit einer bestimmten aktivierten Funktion bereitstellen. Diese Funktionen sind seit Jackson 1.6 verfügbar. Sie müssen also nur die toString()Methode in Ihre Aufzählung schreiben .

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

Weitere enumbezogene Funktionen sind verfügbar, siehe hier:

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

Lagivan
quelle
10
Ich bin mir nicht sicher, warum Sie die Klasse erweitern müssen. Sie können diese Funktion für eine Instanz des ObjectMapper aktivieren.
mttdbrd
+1, weil er mich auf den [READ | WRITE] _ENUMS_USING_TO_STRING verwies, den ich in Spring application.yml
HelLViS69
8

Versuche dies.

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
quelle
6

Sie können die Deserialisierung für jedes Attribut anpassen.

Deklarieren Sie Ihre Deserialize-Klasse mit der AnnotationJsonDeserialize ( import com.fasterxml.jackson.databind.annotation.JsonDeserialize) für das Attribut, das verarbeitet wird. Wenn dies eine Aufzählung ist:

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

Auf diese Weise wird Ihre Klasse verwendet, um das Attribut zu deserialisieren. Dies ist ein vollständiges Beispiel:

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
quelle
Nathaniel Ford, wurde es besser?
Fernando Gomes
1
Ja, das ist eine viel bessere Antwort. es bietet einen gewissen Kontext. Ich würde jedoch noch weiter gehen und diskutieren, warum das Hinzufügen von Deserialisierung auf diese Weise das spezifische Hindernis des OP angeht.
Nathaniel Ford
5

Es gibt verschiedene Ansätze, mit denen Sie die Deserialisierung eines JSON-Objekts in eine Aufzählung durchführen können. Mein Lieblingsstil ist es, eine innere Klasse zu bilden:

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
quelle
4

Hier ist ein weiteres Beispiel, das Zeichenfolgenwerte anstelle einer Karte verwendet.

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
quelle
4

Im Kontext einer Aufzählung @JsonValuefunktioniert die Verwendung von now (seit 2.0) für die Serialisierung und Deserialisierung.

Nach den Jackson-Annotations Javadoc für@JsonValue :

ANMERKUNG: Bei Verwendung für Java-Enums besteht eine zusätzliche Funktion darin, dass der von der mit Anmerkungen versehenen Methode zurückgegebene Wert auch als der Wert betrachtet wird, von dem die Deserialisierung durchgeführt werden soll, und nicht nur der JSON-String, als den die Serialisierung durchgeführt werden soll. Dies ist möglich, da der Satz von Enum-Werten konstant ist und die Zuordnung definiert werden kann, dies ist jedoch für POJO-Typen im Allgemeinen nicht möglich. Daher wird dies nicht für die POJO-Deserialisierung verwendet.

Die EventAnnotation der Aufzählung wie oben funktioniert also (sowohl für die Serialisierung als auch für die Deserialisierung) mit Jackson 2.0+.

Brice Roncace
quelle
3

Neben @JsonSerialize @JsonDeserialize können Sie im Object Mapper auch SerializationFeature und DeserializationFeature (Jackson-Bindung) verwenden.

Beispiel: DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_USING_DEFAULT_VALUE, die den Standardaufzählungstyp angeben, wenn der angegebene nicht in der Aufzählungsklasse definiert ist.

Yuantao
quelle
0

Der einfachste Weg, den ich gefunden habe, ist die Verwendung der Annotation @ JsonFormat.Shape.OBJECT für die Aufzählung.

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

In meinem Fall hat dies Folgendes gelöst:

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

Serialisiert und deserialisiert den folgenden JSON:

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

Ich hoffe, es hilft!

Flavio Sousa
quelle