Сериализация Джексона и DeSerializer

Я использую JAVA 1.6 и Jackson 1.9.9 У меня есть 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;
    }
}

Я добавил @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

Что мне здесь не хватает?

19 ответов

Решение

Решение для сериализатора / десериализатора, указанное xbakesx, является отличным решением, если вы хотите полностью отделить класс enum от его представления JSON.

В качестве альтернативы, если вы предпочитаете автономное решение, реализация, основанная на аннотациях @JsonCreator и @JsonValue, была бы более удобной.

Таким образом, на примере Стенли ниже приводится полное автономное решение (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
    }
}

Обратите внимание, что с этого коммита в июне 2015 года (Jackson 2.6.2 и выше) вы теперь можете просто написать:

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

Вы должны создать статический метод фабрики, который принимает один аргумент и комментирует его @JsonCreator (доступно с версии Jackson 1.2)

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

Узнайте больше об аннотации JsonCreator здесь.

Фактический ответ:

Используемый по умолчанию десериализатор для перечислений .name() десериализовать, поэтому он не использует @JsonValue, Поэтому, как указал @OldCurmudgeon, вам нужно будет передать {"event": "FORGOT_PASSWORD"} чтобы соответствовать .name() значение.

Другой вариант (если вы хотите, чтобы значения json для записи и чтения были одинаковыми)...

Больше информации:

Существует (еще) другой способ управления процессом сериализации и десериализации с Джексоном. Вы можете указать эти аннотации, чтобы использовать свой собственный сериализатор и десериализатор:

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

Тогда вы должны написать MySerializer а также MyDeserializer которые выглядят так:

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

Я нашел очень хорошее и краткое решение, особенно полезное, когда вы не можете изменить перечисляемые классы, как это было в моем случае. Затем вы должны предоставить пользовательский 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);
    }
}

Доступно больше функций, связанных с enum, смотрите здесь:

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

Мне нравится принятый ответ . Однако я бы немного его улучшил (учитывая, что сейчас доступна Java выше 6-й версии).

Пример:

          public enum Operation {
        EQUAL("eq"),
        NOT_EQUAL("ne"),
        LESS_THAN("lt"),
        GREATER_THAN("gt");

        private final String value;

        Operation(String value) {
            this.value = value;
        }

        @JsonValue
        public String getValue() {
            return value;
        }

        @JsonCreator
        public static Operation forValue(String value) {
            return Arrays.stream(Operation.values())
                .filter(op -> op.getValue().equals(value))
                .findFirst()
                .orElseThrow(); // depending on requirements: can be .orElse(null);
        }
    }

Попробуй это.

public enum Event {

   FORGOT_PASSWORD ("забыл пароль");

    частное окончательное строковое значение;

    частное событие (окончательное описание строки) {
        this.value = описание;
    }

    частное мероприятие () {
        this.value = this.name();
    }

    @JsonValue
    окончательное строковое значение () {
        вернуть this.value;
    }
}

Вы можете настроить десериализацию для любого атрибута.

Объявите свой десериализованный класс, используя annotationJsonDeserialize (import com.fasterxml.jackson.databind.annotation.JsonDeserialize) для атрибута, который будет обработан. Если это Enum:

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

Вот еще один пример, который использует строковые значения вместо карты.

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

Существуют различные подходы, которые можно использовать для выполнения десериализации объекта JSON в enum. Мой любимый стиль - сделать внутренний класс:

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

В контексте перечисления, используя @JsonValue сейчас (с 2.0) работает для сериализации и десериализации.

Согласно Джексон-аннотации Javadoc для@JsonValue:

ПРИМЕЧАНИЕ: при использовании для перечислений Java одной дополнительной особенностью является то, что значение, возвращаемое аннотированным методом, также считается значением для десериализации, а не просто строкой JSON для сериализации. Это возможно, так как набор значений Enum является постоянным и возможно определить отображение, но не может быть сделано вообще для типов POJO; как таковой, он не используется для десериализации POJO.

Итак, имея Event enum, аннотированный так же, как и выше, работает (как для сериализации, так и для десериализации) с Jackson 2.0+.

Помимо использования @JsonSerialize @JsonDeserialize, вы также можете использовать SerializationFeature и DeserializationFeature (привязка Джексона) в сопоставителе объектов.

Например, DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_USING_DEFAULT_VALUE, которые дают тип перечисления по умолчанию, если предоставленный тип не определен в классе перечисления.

Здесь «значение» действует как десериализатор, а «пространство имен» — как сериализатор. Следовательно, вы можете передать значение «Студент отсутствует» в API при сохранении, и в БД оно будет сохранено как «STUDENT_ABSENT». С другой стороны, при получении данных в вашем классе ваш API вернет «Студент отсутствует».

      import com.fasterxml.jackson.annotation.JsonProperty;
public enum AttendanceEnums {
    STUDENT_PRESENT,
    @JsonProperty(value = "Student Absent", namespace = "Student Absent")
    STUDENT_ABSENT;
}

Я искал решение для сериализации enum и, наконец, нашел решение.

https://github.com/sirgilligan/EnumerationSerialization

https://digerati-illuminatus.blogspot.com/2022/10/java-enum-generic-serializer-and.html

Он использует новую аннотацию и два новых класса, EnumerationSerializer и EnumerationDeserializer. Вы можете создать подкласс EnumerationDeserializer и создать класс, который устанавливает класс перечисления (типичный подход), или вы можете аннотировать перечисление, и вам не нужно иметь подкласс EnumerationDeserializer.

      @JsonSerialize(using = EnumerationSerializer.class)
@JsonDeserialize(using = EnumerationDeserializer.class)
@EnumJson(serializeProjection = Projection.NAME, deserializationClass = RGB.class)
enum RGB {
    RED,
    GREEN,
    BLUE
}

Обратите внимание, как реализация ContextualDeserializer извлекает класс из аннотации.

https://github.com/sirgilligan/EnumerationSerialization/blob/main/src/main/java/org/example/EnumerationDeserializer.java

Здесь много хорошего кода, который может дать понимание.

Для вашего конкретного вопроса вы можете сделать это:

      @JsonSerialize(using = EnumerationSerializer.class)
@JsonDeserialize(using = EnumerationDeserializer.class)
@EnumJson(serializeProjection = Projection.NAME, deserializationClass = Event.class)
public enum Event {
    FORGOT_PASSWORD("forgot password");

    //This annotation is optional because the code looks for value or alias.
    @EnumJson(serializeProjection = Projection.VALUE)
    private final String value;

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

}

Или вы можете сделать это:

      @JsonSerialize(using = EnumerationSerializer.class)
@JsonDeserialize(using = EnumerationDeserializer.class)
@EnumJson(serializeProjection = Projection.NAME, deserializationClass = Event.class)
public enum Event {
    FORGOT_PASSWORD("forgot password");

    private final String value;

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

}

Это все, что вам нужно сделать.

Затем, если у вас есть класс, который «имеет» событие, вы можете аннотировать каждое событие, чтобы сериализовать его так, как вы хотите.

      class EventHolder {
    @EnumJson(serializeProjection = Projection.NAME)
    Event someEvent;

    @EnumJson(serializeProjection = Projection.ORDINAL)
    Event someOtherEvent;

    @EnumJson(serializeProjection = Projection.VALUE)
    Event yetAnotherEvent;
}

У меня так получилось:

// Your JSON
{"event":"forgot password"}

// Your class to map 
public class LoggingDto {
    @JsonProperty(value = "event")
    private FooEnum logType;
}

//Your enum
public enum FooEnum {

    DATA_LOG ("Dummy 1"),
    DATA2_LOG ("Dummy 2"),
    DATA3_LOG ("forgot password"),
    DATA4_LOG ("Dummy 4"),
    DATA5_LOG ("Dummy 5"),
    UNKNOWN ("");

    private String fullName;

    FooEnum(String fullName) {
        this.fullName = fullName;
    }

    public String getFullName() {
        return fullName;
    }

    @JsonCreator
    public static FooEnum getLogTypeFromFullName(String fullName) {
        for (FooEnum logType : FooEnum.values()) {
            if (logType.fullName.equals(fullName)) {
                return logType;
            }
        }
        return UNKNOWN;
    }


}

Таким образом, значение свойства logType для класса LoggingDto будет DATA3_LOG.

В моем случае это решило:

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

Я надеюсь, что это помогает!

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

 PHONE(1, "Phone"), MAIL(2, "mail"), PERSONAL_EMAIL(3, "Personal email");

private static List<LoginOptionType> all;

static {
    all = new ArrayList<LoginOptionType>() {
        {
            add(LoginOptionType.PHONE);
            add(LoginOptionType.MAIL);
            add(LoginOptionType.PERSONAL_EMAIL);
        }
    };
}

private final Integer viewValue;

private final String name;

LoginOptionType(Integer viewValue, String name) {
    this.viewValue = viewValue;
    this.name = name;
}

public Integer getViewValue() {
    return viewValue;
}

public String getName() {
    return name;
}

public static List<LoginOptionType> getAll() {
    return all;
}
}

Ответ

      [
{
    "viewValue": 1,
    "name": "Phone"
},
{
    "viewValue": 2,
    "name": "mail"
},
{
    "viewValue": 3,
    "name": "Personal email"
}
]

Самый простой способ, который я нашел, - это использование аннотации @JsonFormat.Shape.OBJECT для перечисления.

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

Это старый пост, но если он может кому-то помочь, используйте JsonFormat.Shape.STRING

      @JsonFormat(shape = JsonFormat.Shape.STRING)
public enum SomeEnum{
    @JsonProperty("SOME_PROPERTY")
    someProperty,
    ...
}

Результаты кода выглядят так

      {"someenum":"SOME_PROPERTY"}
Другие вопросы по тегам