2012-09-18 83 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; 
    } 
    } 
}