2012-09-18 51 views
128

我使用JAVA 1.6和杰克逊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 

缺少什么我在这里?

+2

你试过'{“Event”:“FORGOT_PASSWORD”}'?请注意事件和FORGOT_PASSWORD上的大写字母。 – OldCurmudgeon

+0

类似的:[如何使用杰克逊JSON来注释为反序列化枚举字段(https://stackoverflow.com/questions/9300191/how-to-annotate-enum-fields-for-deserialization-using-jackson-json) – Vadzim

回答

167

如果您希望将yor枚举类与其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 
    } 
} 
+6

仅添加@JsonValue为我工作。谢谢。 – Alessandro

+0

@Agusti请看看我的问题,我缺少的是有http://stackoverflow.com/questions/30525986/enum-is-not-binding –

+8

可能明显一些,但要注意,@ JsonValue用于系列化和@ JsonCreator进行反序列化。如果你不这样做,你只需要一个或另一个。 – acvcu

35

实际答:

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

的另一种选择(假设你想要写和读的JSON值是相同的)...

更多信息:

有(又)另一种方式来管理序列化和与杰克逊的反序列化过程。您可以指定这些批注使用自己定制的串行器和解串:

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

的自定义(de)序列化程序的使用杀死了简单性(这正是使用Jackson值得的,因此),所以这是在真正沉重的情况下所需要的。使用@JsonCreator,如下所述,并选中[此评论](http://jira.codehaus.org/browse/JACKSON-861) –

+1

这soluiton最好在有机磷农药的问题推出了有点疯狂的问题。这里真正的问题是OP想要以*渲染*形式返回结构化数据。也就是说,他们正在返回已经包含用户友好字符串的数据。但为了将呈现的表单重新转换为标识符,我们需要一些能够反转转换的代码。哈克接受的答案想要使用地图来处理转换,但需要更多的维护。有了这个解决方案,你可以添加新的枚举类型,然后你的开发人员可以继续他们的工作。 – mttdbrd

68

您应该创建一个静态工厂方法这需要一个参数与@JsonCreator注释它(因为杰克逊1.2可用)

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

了解更多关于JsonCreator注释here

+8

这是最干净和最简洁的解决方案,其余的只是吨的样板,可以(并应该!)不惜一切代价避免! –

+1

'@ JSONValue'来序列化和'@ JSONCreator'来反序列化。 – Chiranjib

23

我发现一个非常漂亮和简洁的解决方案,尤其是有用的,当你不能修改枚举类,因为它是在我的情况。然后,您应该提供一个自定义的ObjectMapper,并启用某个功能。那些功能从杰克逊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

+3

不确定为什么你需要延长班级。您可以在ObjectMapper的实例上启用此功能。 – mttdbrd

+0

+1,因为他指向我可以在Spring中使用的[READ | WRITE] _ENUMS_USING_TO_STRING application.yml – HelLViS69

+0

到wiki.fasterxml.com的链接不再工作 –

111

注意,由于this commit在2015年6月(杰克逊2.6.2及以上版本),你现在可以简单地编写:

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

升级时间。 –

+1

不错的解决方案。这是一个耻辱我坚持2.6.0绑定在Dropwizard :-( –

+0

这将只序列化,而不是反序列化。 –

1

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

使用将要处理的属性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; 
    } 
} 
+0

您应该解释*为什么*这项工作。 –

+0

纳撒尼尔福特,得到了更好吗? –

+1

是的,这是一个更好的答案;它提供了一些上下文,但我会更进一步,但讨论为什么以这种方式增加反序列化解决了OP的具体障碍。 –

3

这是另一个使用字符串值而不是地图的例子。

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

有多种方法可以完成将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; 
    } 
    } 
}