2013-05-08 14 views
13

mam coś JSON obiektów takich jak:Jak zignorować pola wyliczeniowe w mapowaniu Jackson JSON-to-Object?

{"name":"John", "grade":"A"} 

lub

{"name":"Mike", "grade":"B"} 

lub

{"name":"Simon", "grade":"C"} 

itp

staram się odwzorować powyższy JSON do:

@JsonIgnoreProperties(ignoreUnknown = true) 
public class Employee{ 
     @JsonIgnoreProperties(ignoreUnknown = true) 
     public enum Grade{ A, B, C } 
     Grade grade; 
     String name; 

    public Grade getGrade() { 
    return grade; 
    } 

    public void setGrade(Grade grade) { 
    this.grade = grade; 
    } 

    public String getName() { 
    return name; 
    } 

    public void setName(String name) { 
    this.name = name; 
    } 
} 

wyżej mapowanie działa dobrze, ale w przyszłości będzie więcej rodzajów „gatunek” powiedzmy D, E itd który łamie istniejące mapowanie i rzuca następujący wyjątek

05-08 09:56:28.130: W/System.err(21309): org.codehaus.jackson.map.JsonMappingException: Can not construct instance of Employee from String value 'D': value not one of declared Enum instance names 

Czy istnieje sposób, aby ignorować nieznane pola za pomocą typów enum?

Dzięki

+2

Adnotacja '@ JsonIgnoreProperties' nie działa w sposób oczekiwany dla typów' Enum'; tylko (obecnie) pozwala ignorować nieznane właściwości POJO. Ale myślę, że ma to sens jako pomysł na ulepszenie. Czy mógłbyś zgłosić problem w projekcie [Jackson databind] (https://github.com/FasterXML/jackson-databind)? Jeśli tak, może może być dodane w Jackson 2.3? – StaxMan

+3

Odpowiedzi są nieco pełne. Dla tych, którzy szukają najszybszego i najprostszego rozwiązania: ustaw "READ_UNKNOWN_ENUM_VALUES_AS_NULL" [funkcja deserializacji] (https://github.com/FasterXML/jackson-databind/wiki/Deserialization-Features#value-conversions) na true. – Jonik

Odpowiedz

19

myślę, że należy określić zewnętrzny deserializer dla Grade wyliczenia.

dodałem dodatkowe pole do ENUM - nieznany:

enum Grade { 
    A, B, C, UNKNOWN; 

    public static Grade fromString(String value) { 
     for (Grade grade : values()) { 
      if (grade.name().equalsIgnoreCase(value)) { 
       return grade; 
      } 
     } 

     return UNKNOWN; 
    } 
} 
class Employee { 

    @JsonDeserialize(using = GradeDeserializer.class) 
    private Grade grade; 
    private String name; 

    public Grade getGrade() { 
     return grade; 
    } 

    public void setGrade(Grade grade) { 
     this.grade = grade; 
    } 

    public String getName() { 
     return name; 
    } 

    public void setName(String name) { 
     this.name = name; 
    } 

    @Override 
    public String toString() { 
     return "Employee [grade=" + grade + ", name=" + name + "]"; 
    } 
} 

Teraz parser może wyglądać tak:

class GradeDeserializer extends JsonDeserializer<Grade> { 
    @Override 
    public Grade deserialize(JsonParser parser, DeserializationContext context) 
      throws IOException, JsonProcessingException { 
     return Grade.fromString(parser.getValueAsString()); 
    } 
} 

Przykład użycia:

public class JacksonProgram { 

    public static void main(String[] args) throws Exception { 
     ObjectMapper objectMapper = new ObjectMapper(); 
     JsonFactory jsonFactory = new JsonFactory(); 
     JsonParser parser = jsonFactory 
       .createJsonParser("{\"name\":\"John\", \"grade\":\"D\"}"); 
     Employee employee = objectMapper.readValue(parser, Employee.class); 
     System.out.println(employee); 
    } 

} 

wyjściowa:

Employee [grade=UNKNOWN, name=John] 

Jeśli nie chcesz dodawać dodatkowego pola, zwrócisz na przykład null.

+0

Dzięki, zadziałało. – Saqib

+1

Posiadanie NIEZNANYCH wystąpień enumu wydaje się dla mnie złym wyborem. Wolałbym rozwiązanie Stefaana od funkcji READ_UNKNOWN_ENUM_VALUES_AS_NULL, która wytworzy wartość null. – Tom

+3

NIEZNANY oznacza, że ​​wartość nie jest znana w tej chwili. null oznacza - wartość nie jest ustawiona. Może to być mylące w niektórych sytuacjach i może powodować NPE. Ponadto, jeśli wartości wyliczeniowe zawierają metody, możemy zaimplementować specjalną obsługę zerową w bardziej elegancki sposób. Dla mnie rozwiązanie z funkcją READ_UNKNOWN_ENUM_VALUES_AS_NULL jest również dobre. Może, jest lepszy niż mój ... –

19

Znalazłem sposób to zrobić to w następujący sposób:

public static void main(String[] args) throws JsonParseException, JsonMappingException, UnsupportedEncodingException, IOException { 
    String json = "{\"name\":\"John\", \"grade\":\"D\"}"; 

    ObjectMapper mapper = new ObjectMapper(); 
    mapper.configure(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_AS_NULL, true); 
    Employee employee = mapper.readValue(new ByteArrayInputStream(json.getBytes("UTF-8")), Employee.class); 

    System.out.println(employee.getGrade()); 
} 

This wyjścia:

zerowe

innych klas:

import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 

@JsonIgnoreProperties(ignoreUnknown = true) 
public class Employee { 
    private String name; 
    private Grade grade; 

    public String getName() { 
     return name; 
    } 

    public void setName(String name) { 
     this.name = name; 
    } 

    public Grade getGrade() { 
     return grade; 
    } 

    public void setGrade(Grade grade) { 
     this.grade = grade; 
    } 
} 



import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 

@JsonIgnoreProperties(ignoreUnknown = true) 
public enum Grade {A, B, C} 

I haven t com e na drodze do tego z jeszcze adnotacją.

Mam nadzieję, że to pomoże.

+0

Dzięki, zadziałało – Saqib

+0

@stefaan: Ta odpowiedź zadziałała dla mnie! Wszystko, czego mi brakowało, to DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_AS_NULL, świetne znalezisko! – Arthulia

+0

Od wersji Jackson 2.8 istnieje również READ_UNKNOWN_ENUM_VALUES_USING_DEFAULT_VALUE, która pozwala ustawić domyślną wartość zamiast wartości null. https://fasterxml.github.io/jackson-databind/javadoc/2.8/com/fasterxml/jackson/databind/DeserializationFeature.html#READ_UNKNOWN_ENUM_VALUES_USING_DEFAULT_VALUE – Rick

1

@JsonCreator zapewnia bardziej zwięzłe rozwiązanie w porównaniu do @JsonDeserialize.

Pomysł polega na dodaniu adnotacji do zamiany valueOf() (zwanej safeValueOf() w tym przykładzie) z @JsonCreator, a następnie Jackson deserializował ciągi znaków za pomocą implementacji.

Należy zauważyć, że implementacja znajduje się w wyliczeniu, można jej użyć jako pola w innych obiektach bez zmian.

Poniższe rozwiązanie jest zapakowane w test jednostkowy, dzięki czemu można go uruchomić bezpośrednio.

import static junit.framework.TestCase.assertEquals; 

import java.io.IOException; 

import org.junit.Test; 

import com.fasterxml.jackson.annotation.JsonCreator; 
import com.fasterxml.jackson.databind.ObjectMapper; 

public class EmployeeGradeTest { 

    public enum Grade { 
     A, B, C, OTHER; 

     @JsonCreator 
     public static Grade safeValueOf(String string) { 
      try { 
       return Grade.valueOf(string); 
      } catch (IllegalArgumentException e) { 
       return OTHER; 
      } 
     } 
    } 

    @Test 
    public void deserialize() throws IOException { 
     assertEquals(Grade.A, new ObjectMapper().readValue("\"A\"", Grade.class)); 
    } 

    @Test 
    public void deserializeNewValue() throws IOException { 
     assertEquals(Grade.OTHER, new ObjectMapper().readValue("\"D\"", Grade.class)); 
    } 
}