2014-09-04 21 views
6

Więc mam JSON, który wygląda takKtóry @ JsonTypeInfo.ID wybrać dla właściwości = "typ.id" do deserializacji, JsonTypeInfo.Id.CUSTOM?

{ 
    "ActivityDisplayModel" : { 
     "name" : "lunch with friends", 
     "startTime" : "12:00:00", 
     "type" : { 
      "id" : "MEAL", 
      "description" : "Meal" 
     }, 
     "complete" : false 
    } 
} 

i staram się znaleźć sposób, aby uzyskać @JsonTypeInfo aby nie być na mnie zły za to, że parametr typu wewnątrz obiektu type. Mam to działa wcześniej, gdy pole type było ciągiem znaków, a nie obiektem, ale do późniejszego przetworzenia potrzebuję go jako obiektu. Wiem, że następujące nie działa, i domyślam się, że istnieje sposób na użycie JsonTypeInfo.Id.CUSTOM, ale po obejrzeniu wszystkich w Internecie nie pojawiły się pełne przykłady z JSON. Ponadto, jeśli jest to możliwe przy ustawieniu objectMapper, mam tylko uszy.

/** 
* My ActivityDisplayModel Abstract Class 
*/ 
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type.id") 
@JsonSubTypes({ 
@JsonSubTypes.Type(value = MealDisplayModel.class, name = "MEAL"), 
@JsonSubTypes.Type(value = EntertainmentDisplayModel.class, name = "ENTERTAINMENT") 
}) 
public abstract class ActivityDisplayModel { 
    ... 

Powyższe esentially co chcę zrobić, ale oczywiście mam wyjątek:

Could not read JSON: Could not resolve type id '{' into a subtype of [simple type, class ... .ActivityDisplayModel] 

Dla takiego prostego problemu po prostu patrząc jeden poziom głębiej w JSON, który będzie Myślałeś, że to byłby problem?

+0

Wszelkie wskazówki od @ProgrammerBruce właśnie sprawią, że mój dzień się zmieni :)! –

+0

To samo dotyczy tego doskonałego faceta @Staxman! –

+0

Znalezione pytanie wygląda tak samo od około 3 lat wstecz http://stackoverflow.com/questions/7502972/jackson-deserialization-unexpected-token-end-object –

Odpowiedz

2

Nie jestem pewien, czy można to zrobić, określając właściwość wewnętrzną: type.id. Moim zdaniem powinieneś zmienić swoją JSON na prostszą wersję. Jeśli nie możesz zmusić swojego dostawcy do zmiany schematu JSON, musisz to zrobić ręcznie. Załóżmy, że Twój JSON wygląda jak poniżej:

{ 
    "activityDisplayModel": { 
     "name": "lunch with friends", 
     "type": { 
      "id": "MEAL", 
      "description": "Meal" 
     }, 
     "complete": false 
    } 
} 

Poniżej POJO s klas nadające się do powyżej JSON:

@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type") 
@JsonSubTypes({ 
    @JsonSubTypes.Type(value = MealDisplayModel.class, name = "MEAL"), 
    @JsonSubTypes.Type(value = EntertainmentDisplayModel.class, name = "ENTERTAINMENT") 
}) 
abstract class ActivityDisplayModel { 

    protected String name; 

    public String getName() { 
     return name; 
    } 

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

    @Override 
    public String toString() { 
     return name; 
    } 
} 

class MealDisplayModel extends ActivityDisplayModel { 

    private boolean complete; 

    public boolean isComplete() { 
     return complete; 
    } 

    public void setComplete(boolean complete) { 
     this.complete = complete; 
    } 

    @Override 
    public String toString() { 
     return "MealDisplayModel [complete=" + complete + ", toString()=" + super.toString() + "]"; 
    } 
} 

@JsonIgnoreProperties("complete") 
class EntertainmentDisplayModel extends ActivityDisplayModel { 

    @Override 
    public String toString() { 
     return "EntertainmentDisplayModel [toString()=" + super.toString() + "]"; 
    } 
} 

class Root { 

    private ActivityDisplayModel activityDisplayModel; 

    public ActivityDisplayModel getActivityDisplayModel() { 
     return activityDisplayModel; 
    } 

    public void setActivityDisplayModel(ActivityDisplayModel activityDisplayModel) { 
     this.activityDisplayModel = activityDisplayModel; 
    } 

    @Override 
    public String toString() { 
     return activityDisplayModel.toString(); 
    } 
} 

Poniżej skrypt pokazuje, w jaki sposób można analizować powyżej JSON:

ObjectMapper mapper = new ObjectMapper(); 
// Updated JSON in memory 
ObjectNode rootNode = (ObjectNode)mapper.readTree(json); 
ObjectNode activityDisplayModelNode = (ObjectNode)rootNode.path("activityDisplayModel"); 
JsonNode typeNode = activityDisplayModelNode.path("type"); 
activityDisplayModelNode.set("type", typeNode.path("id")); 

System.out.println("Result: " + mapper.convertValue(rootNode, Root.class)); 

Powyższy skrypt wydruki:

Result: MealDisplayModel [complete=false, toString()=lunch with friends] 

zobacz także:

  1. Jackson Tree Model Example.
  2. Convert Java Object to JsonNode in Jackson.
+0

Dzięki @ MichałZiober. Musiałem zachować "typ" obiektu b/c zapisujemy go jako taki do naszej bazy danych (chyba, że ​​przepisuję serwis współpracowników i DAO). To, co zrobiłem, pozostawił oryginalny "typ" jako obiekt, ale także wyodrębnił "id" do nowego pola o nazwie "typeID", które @JsonTypeInfo zbiera teraz za pomocą 'property =" typeId "'. Teraz trafiam na problemy w 'databind.deser.std.EnumDeserializer', ale jest to kolejny problem z jacksonem do debugowania. –

+0

Myślę, że zrobiłeś to dobrze! Dobra decyzja. Moje rozwiązanie to najnowsza opcja, z której powinieneś skorzystać. –

1

Wiem, że minęły 3 lata od pierwotnego pytania, ale właściwości zagnieżdżone są nadal nieobsługiwane i być może to komuś pomoże. W końcu utworzyłem klasę NestedTypeResolver, abyśmy mogli używać składni kropki zgodnie z oczekiwaniami. Wystarczy dodać @JsonTypeResolver(NestedTypeResolver.class) do dowolnej klasy z zagnieżdżonych dyskryminatorów a autora oryginalna próba będzie działać:

/** 
* My ActivityDisplayModel Abstract Class 
*/ 
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type.id") 
    @JsonSubTypes({ 
    @JsonSubTypes.Type(value = MealDisplayModel.class, name = "MEAL"), 
    @JsonSubTypes.Type(value = EntertainmentDisplayModel.class, name = "ENTERTAINMENT") 
}) 
@JsonTypeResolver(NestedTypeResolver.class) 
public abstract class ActivityDisplayModel { 

NestedTypeResolver:

/** 
* Allows using nested "dot" dyntax for type discriminators. To use, annotate class with @JsonTypeResolver(NestedTypeResolver.class) 
*/ 
public class NestedTypeResolver extends StdTypeResolverBuilder { 
    @Override 
    public TypeDeserializer buildTypeDeserializer(DeserializationConfig config, JavaType baseType, 
      Collection<NamedType> subtypes) { 
      //Copied this code from parent class, StdTypeResolverBuilder with same method name 
      TypeIdResolver idRes = idResolver(config, baseType, subtypes, false, true); 
      return new NestedTypeDeserializer(baseType, idRes, _typeProperty, _typeIdVisible, 
       null, _includeAs); 
    } 
} 

Wszystko ciężka praca odbywa się tu, NestedTypeDeserializer:

/** 
* Heavy work to support {@link NestedTypeResolver} 
*/ 
public class NestedTypeDeserializer extends AsPropertyTypeDeserializer { 

    private static final Logger LOGGER = LoggerFactory.getLogger(NestedTypeDeserializer.class); 

    public NestedTypeDeserializer(JavaType bt, 
      TypeIdResolver idRes, String typePropertyName, boolean typeIdVisible, 
      JavaType defaultImpl) { 
     super(bt, idRes, typePropertyName, typeIdVisible, defaultImpl); 
    } 

    public NestedTypeDeserializer(JavaType bt, TypeIdResolver idRes, String typePropertyName, boolean typeIdVisible, 
      JavaType defaultImpl, JsonTypeInfo.As inclusion) { 
     super(bt, idRes, typePropertyName, typeIdVisible, defaultImpl, inclusion); 
    } 

    public NestedTypeDeserializer(AsPropertyTypeDeserializer src, BeanProperty property) { 
     super(src, property); 
    } 

    @Override 
    public TypeDeserializer forProperty(BeanProperty prop) { 
     return (prop == _property) ? this : new NestedTypeDeserializer(this, prop); 
    } 

    @Override 
    public Object deserializeTypedFromObject(JsonParser p, DeserializationContext ctxt) throws IOException { 
     JsonNode originalNode = p.readValueAsTree(); 
     JsonNode node = originalNode; 
     //_typePropertyName is the dot separated value of "property" in @JsonTypeInfo 
     LOGGER.debug("Searching for type discriminator [{}]...", _typePropertyName); 
     for (String property : _typePropertyName.split("\\.")) { //traverse down any nested properties 
      JsonNode nestedProp = node.get(property); 
      if (nestedProp == null) { 
       ctxt.reportWrongTokenException(p, JsonToken.FIELD_NAME, 
         "missing property '" + _typePropertyName + "' that is to contain type id (for class " 
           + baseTypeName() + ")"); 
       return null; 
      } 
      node = nestedProp; 
     } 
     LOGGER.debug("Found [{}] with value [{}]", _typePropertyName, node.asText()); 
     JsonDeserializer<Object> deser = _findDeserializer(ctxt, "" + node.asText()); 
     //Since JsonParser is a forward-only operation and finding the "type" discriminator advanced the pointer, we need to reset it 
     //Got clues from https://www.dilipkumarg.com/dynamic-polymorphic-type-handling-jackson/ 
     JsonParser jsonParser = new TreeTraversingParser(originalNode, p.getCodec()); 
     if (jsonParser.getCurrentToken() == null) { 
      jsonParser.nextToken(); 
     } 
     return deser.deserialize(jsonParser, ctxt); 
    } 
} 

Nota prawna: używaliśmy tego przez miesiąc z Jackson 2.8.10 i nie mieliśmy żadnych problemów, ale musieliśmy zagłębić się w chrapy kodu źródłowego Jacksona aby to osiągnąć, więc YMMV. Miejmy nadzieję, że Jackson pozwoli na to natychmiast po wyjęciu z pudełka, więc nie potrzebujemy tych obejść.