Jackson enum Serializing和DeSerializer

poo*_*man 208 java enums jsonserializer jackson

我正在使用JAVA 1.6和Jackson 1.9.9我有一个枚举

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

我添加了一个@JsonValue,这似乎完成了将对象序列化为:

{"event":"forgot password"}
Run Code Online (Sandbox Code Playgroud)

但是当我尝试反序列化时,我得到了一个

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
Run Code Online (Sandbox Code Playgroud)

我在这里错过了什么?

Agu*_*hez 266

xbakesx指出的串行器/解串器解决方案是一个很好的解决方案,如果你想完全解耦yor枚举类和它的JSON表示.

或者,如果您更喜欢自包含的解决方案,则基于@JsonCreator和@JsonValue注释的实现将更加方便.

因此,利用Stanley的示例,以下是一个完整的自包含解决方案(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
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 对某些人来说可能是显而易见的,但请注意@JsonValue用于序列化,@ JsonCreator用于反序列化.如果你不做两件事,你只需要一个或另一个. (21认同)
  • 只添加@JsonValue对我有用.谢谢. (9认同)
  • 我真的不喜欢这个解决方案,因为你引入了两个真理来源.开发人员总是要记得在两个地方添加名称.我更喜欢一种解决方案,它可以做正确的事情,而无需用地图装饰枚举的内部结构. (6认同)
  • 您可以使用 YourEnum.values() 代替静态地图,它提供 YourEnum 数组并对其进行迭代 (4认同)
  • @mttdbrd,您可以通过在构造函数期间将对象添加到地图来避免这种情况 (2认同)
  • @ttdbrd统一事实如何?https://gist.github.com/Scuilion/036c53fd7fee2de89701a95822c0fb60 (2认同)

小智 194

请注意,截至2015年6月(Jackson 2.6.2及更高版本)的提交,您现在可以简单地写道:

public enum Event {
    @JsonProperty("forgot password")
    FORGOT_PASSWORD;
}
Run Code Online (Sandbox Code Playgroud)

  • 它适用于序列化和反序列化 (6认同)
  • 似乎根本没有被弃用:https://github.com/FasterXML/jackson-annotations/blob/master/src/main/java/com/fasterxml/jackson/annotation/JsonProperty.java (6认同)
  • 时间升级. (5认同)
  • 此功能自2.8以来已弃用. (2认同)
  • 此解决方案适用于 Enum 上的序列化和反序列化。在 2.8 中测试。 (2认同)
  • 使用 Jackson 2.9.10,这对我没有任何作用。 (2认同)
  • 我添加了 (2.11) 文档的官方链接,其中明确指出“@JsonProperty”可以在 2.6 及更高版本中像这样使用。 (2认同)

Sta*_*ley 86

你应该创建一个静态工厂方法,它接受单个参数并用它注释@JsonCreator(从Jackson 1.2开始可用)

@JsonCreator
public static Event forValue(String value) { ... }
Run Code Online (Sandbox Code Playgroud)

在这里阅读有关JsonCreator注释的更多信息.

  • 这是最干净,最简洁的解决方案,其余的只是大量的样板,可以(而且应该!)不惜一切代价避免! (10认同)
  • `@ JSONValue`序列化和`@ JSONCreator`反序列化. (4认同)
  • @ClintEastwood 是否应该避免其他解决方案取决于您是否想将序列化/反序列化问题与枚举分开。 (2认同)

xba*_*esx 41

实际答案:

枚举的默认反序列化程序用于.name()反序列化,因此它不使用@JsonValue.所以@OldCurmudgeon指出,你需要传入{"event": "FORGOT_PASSWORD"}以匹配该.name()值.

另一个选项(假设您希望写入和读取json值相同)...

更多信息:

还有另一种管理Jackson的序列化和反序列化过程的方法.您可以指定这些注释以使用您自己的自定义序列化程序和反序列化程序:

@JsonSerialize(using = MySerializer.class)
@JsonDeserialize(using = MyDeserializer.class)
public final class MyClass {
    ...
}
Run Code Online (Sandbox Code Playgroud)

然后,你必须编写MySerializerMyDeserializer它看起来像这样:

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
    }

}
Run Code Online (Sandbox Code Playgroud)

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

}
Run Code Online (Sandbox Code Playgroud)

最后一点,特别是对于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;
}
Run Code Online (Sandbox Code Playgroud)

  • 使用自定义(de)序列化程序会破坏简单性(使用Jackson是值得的,顺便说一句),因此在非常繁重的情况下需要这样做.使用@JsonCreator,如下所述,并检查[this comment](http://jira.codehaus.org/browse/JACKSON-861) (3认同)
  • 这个解决方案最适合在 OPs 问题中引入的有点疯狂的问题。这里真正的问题是 OP 想要以 *rendered* 形式返回结构化数据。也就是说,他们正在返回已经包含用户友好字符串的数据。但是为了将呈现的表单重新转换为标识符,我们需要一些可以反转转换的代码。hacky 接受的答案想要使用地图来处理转换,但需要更多的维护。使用此解决方案,您可以添加新的枚举类型,然后您的开发人员可以继续他们的工作。 (2认同)

lag*_*van 31

我找到了一个非常简洁的解决方案,当你不能像我的情况那样修改枚举类时特别有用.然后,您应该提供自定义的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);
    }
}
Run Code Online (Sandbox Code Playgroud)

有更多与枚举相关的功能,请参见此处:

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

  • 不知道为什么你需要扩展课程.您可以在ObjectMapper的实例上启用此功能. (9认同)

小智 15

我喜欢接受的答案。不过,我会稍微改进一下(考虑到现在有高于版本 6 的 Java 可用)。

例子:

    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);
        }
    }
Run Code Online (Sandbox Code Playgroud)


Fer*_*mes 6

您可以为任何属性定制反序列化。

使用注解JsonDeserialize(import com.fasterxml.jackson.databind.annotation.JsonDeserialize)声明将要反序列化的类的属性。如果这是一个枚举:

@JsonDeserialize(using = MyEnumDeserialize.class)
private MyEnum myEnum;
Run Code Online (Sandbox Code Playgroud)

这样,您的类将用于反序列化属性。这是一个完整的示例:

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;
    }
}
Run Code Online (Sandbox Code Playgroud)


Gre*_*ash 5

这是另一个使用字符串值而不是映射的示例。

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;
    }
}
Run Code Online (Sandbox Code Playgroud)


Sam*_*rry 5

您可以采用多种方法将 JSON 对象反序列化为枚举。我最喜欢的风格是做一个内部类:

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;
    }
  }
}
Run Code Online (Sandbox Code Playgroud)


Bri*_*ace 5

在枚举的上下文中,使用@JsonValuenow(自 2.0 起)可用于序列化反序列化。

根据Jackson-annotations javadoc@JsonValue

注意:当用于 Java 枚举时,另一个特性是带注释的方法返回的值也被视为要反序列化的值,而不仅仅是要序列化的 JSON 字符串。这是可能的,因为 Enum 值集是常量,并且可以定义映射,但对于 POJO 类型通常不能这样做;因此,这不用于 POJO 反序列化。

因此,Event在 Jackson 2.0+ 中,按照上面的方式注释枚举(对于序列化和反序列化)。


小智 5

尝试这个。

公共枚举事件{

   FORGOT_PASSWORD(“忘记密码”);

    私有最终String值;

    私人事件(最终字符串描述){
        this.value =说明;
    }

    私人Event(){
        this.value = this.name();
    }

    @JsonValue
    最终的String value(){
        返回this.value;
    }
}