Сериализация Джексона и 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 извлекает класс из аннотации.
Здесь много хорошего кода, который может дать понимание.
Для вашего конкретного вопроса вы можете сделать это:
@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"}