【问题标题】:Jackson enum Serializing and DeSerializer杰克逊枚举序列化和反序列化器
【发布时间】:2012-09-10 05:31:44
【问题描述】:

我正在使用 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;
    }
}

我添加了一个@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

我在这里错过了什么?

【问题讨论】:

标签: java enums jackson jsonserializer


【解决方案1】:

如果您希望将 enum 类与其 JSON 表示完全解耦,@xbakesx 指出的序列化器/反序列化器解决方案是一个很好的解决方案。

或者,如果您更喜欢独立的解决方案,基于@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
    }
}

【讨论】:

  • 对某些人来说可能很明显,但请注意@JsonValue 用于序列化,@JsonCreator 用于反序列化。如果你不同时做这两件事,你只需要一个或另一个。
  • 我真的不喜欢这个解决方案,因为您介绍了两个事实来源。开发人员将始终必须记住在两个位置添加名称。我更喜欢一种解决方案,它只做正确的事情,而不用地图装饰枚举的内部。
  • @mttdbrd 您可以通过在构造函数期间将对象添加到地图来避免这种情况
  • @ttdbrd 这个统一真理怎么样? gist.github.com/Scuilion/036c53fd7fee2de89701a95822c0fb60
  • 您可以使用 YourEnum.values() 而不是静态地图,它会提供 YourEnum 数组并对其进行迭代
【解决方案2】:

请注意,从 2015 年 6 月的this commit(Jackson 2.6.2 及更高版本)开始,您现在可以简单地编写:

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

行为记录在这里:https://fasterxml.github.io/jackson-annotations/javadoc/2.11/com/fasterxml/jackson/annotation/JsonProperty.html

从 Jackson 2.6 开始,此注解也可用于更改 Enum 的序列化,如下所示:

 public enum MyEnum {
      @JsonProperty("theFirstValue") THE_FIRST_VALUE,
      @JsonProperty("another_value") ANOTHER_VALUE;
 }

作为使用 JsonValue 注释的替代方法。

【讨论】:

  • 不错的解决方案。很遗憾我被捆绑在 Dropwizard 中的 2.6.0 卡住了:-(
  • 此解决方案适用于 Enum 上的序列化和反序列化。在 2.8 中测试。
  • 这对我没有任何帮助,使用 Jackson 2.9.10。
  • 我在 (2.11) 文档中添加了一个官方链接,其中明确指出 @JsonProperty 可以在 2.6 及更高版本中像这样使用。
【解决方案3】:

您应该创建一个静态工厂方法,该方法接受单个参数并使用 @JsonCreator 注释它(自 Jackson 1.2 起可用)

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

详细了解 JsonCreator 注释 here

【讨论】:

  • 这是最干净、最简洁的解决方案,其余的只是大量可以(并且应该!)不惜一切代价避免的样板!
  • @JSONValue 进行序列化,@JSONCreator 进行反序列化。
  • @JsonCreator public static Event valueOf(int intValue) { ... }int 反序列化为Event 枚举器。
  • @ClintEastwood 是否应避免使用其他解决方案取决于您是否要将序列化/反序列化问题与枚举分开。
【解决方案4】:

实际答案:

枚举的默认反序列化器使用.name() 进行反序列化,因此它没有使用@JsonValue。正如@OldCurmudgeon 指出的那样,您需要传入{"event": "FORGOT_PASSWORD"} 以匹配.name() 值。

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

更多信息:

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

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

然后你必须写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
    }

}

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

}

最后一点,特别是对使用方法getYourValue() 序列化的枚举JsonEnum 执行此操作时,您的序列化器和反序列化器可能如下所示:

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

【讨论】:

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

我找到了一个非常简洁的解决方案,当您无法像我一样修改枚举类时特别有用。然后,您应该提供一个启用了特定功能的自定义 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);
    }
}

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

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

【讨论】:

  • 不确定为什么需要扩展类。您可以在 ObjectMapper 的实例上启用此功能。
  • +1 因为他指出我可以在 Spring application.yml 中使用的 [READ|WRITE]_ENUMS_USING_TO_STRING
  • 谢谢,您的回答帮助我解决了我在 Retrofit 中的问题
  • 感谢配置命中。它帮助我解决了我的问题。
【解决方案6】:

试试这个。

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

【讨论】:

    【解决方案7】:

    我喜欢accepted answer。不过,我会稍微改进一下(考虑到现在有高于版本 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);
            }
        }
    

    【讨论】:

      【解决方案8】:

      您可以自定义任何属性的反序列化。

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

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

      【讨论】:

      • 纳撒尼尔福特,好点了吗?
      • 是的,这是一个更好的答案;它提供了一些上下文。不过,我会更进一步,讨论为什么以这种方式添加反序列化可以解决 OP 的特定障碍。
      【解决方案9】:

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

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

      【讨论】:

        【解决方案10】:

        您可以采用多种方法将 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;
            }
          }
        }
        

        【讨论】:

          【解决方案11】:

          在枚举的上下文中,现在(从 2.0 开始)使用 @JsonValue 可用于序列化反序列化。

          根据jackson-annotations javadoc for @JsonValue

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

          因此,像上面一样注释 Event 枚举在 jackson 2.0+ 上工作(对于序列化和反序列化)。

          【讨论】:

            【解决方案12】:

            除了使用@JsonSerialize @JsonDeserialize 之外,您还可以在对象映射器中使用 SerializationFeature 和 DeserializationFeature(杰克逊绑定)。

            如 DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_USING_DEFAULT_VALUE,如果提供的枚举类型未在枚举类中定义,则提供默认枚举类型。

            【讨论】:

              【解决方案13】:

              就我而言,这就是解决的问题:

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

              希望对你有帮助!

              【讨论】:

                【解决方案14】:

                我发现最简单的方法是对枚举使用@JsonFormat.Shape.OBJECT 注解。

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

                【讨论】:

                  【解决方案15】:

                  我是这样做的:

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

                  所以 LoggingDto 类的属性“logType”的值将是 DATA3_LOG

                  【讨论】:

                    【解决方案16】:

                    这篇文章很旧,但如果它可以帮助某人,请使用 JsonFormat.Shape.STRING

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

                    代码结果是这样的

                    {"someenum":"SOME_PROPERTY"}
                    

                    【讨论】:

                      【解决方案17】:
                      @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"
                      }
                      ]
                      

                      【讨论】:

                        猜你喜欢
                        • 1970-01-01
                        • 1970-01-01
                        • 2021-09-10
                        • 2016-10-23
                        • 1970-01-01
                        • 1970-01-01
                        • 1970-01-01
                        • 2015-04-07
                        相关资源
                        最近更新 更多