2015-05-05 93 views
4

我有傳入的JSON數據的格式如下@JsonTypeResolver是解決使用多個屬性的唯一選擇嗎?

{ 
    "header": { 
     "schema_id": { 
      "namespace": "omh", 
      "name": "physical-activity", 
     }, 
    }, 
    "body": { 
     "activity_name": "walking", 
     "distance": { 
      "value": 1.5, 
      "unit": "mi" 
     }, 
    } 
} 

和相應的Java類,看起來像

public class DataPoint<T extends Measure> { 

    private DataPointHeader header; 
    private T body; 

@JsonNaming(LowerCaseWithUnderscoresStrategy.class) 
public class PhysicalActivity extends Measure { 

    private String activityName; 
    private LengthUnitValue distance; 

我想傑克遜解決bodyPhysicalActivity基於JSON文檔中的schema_id類型,例如在僞

if schema_id.namespace == 'omh' && schema_id.name == 'physical-activity' 
    then return PhysicalActivity.class 

我試着@JsonTypeIdResolver這樣做,但如果我嘗試導航到header.schema_id.name@JsonTypeInfo,例如

@JsonTypeInfo(use = JsonTypeInfo.Id.CUSTOM, 
     include = JsonTypeInfo.As.EXTERNAL_PROPERTY, 
     property = "header.schema_id.name") 
@JsonTypeIdResolver(DataPointTypeIdResolver.class) 
public abstract class Measure { 

我得到一個missing property: 'header.schema_id.name'錯誤。即使我可以,我也不認爲我可以對namespacename屬性做出決定。

除了從頭開始搭建@JsonTypeResolver之外,有沒有一種理智的方式來做到這一點?

回答

2

在傑克遜的源代碼中似乎有很多假設,類型ID是字符串,所以我懷疑JsonTypeResolver是一種方式去...雖然它看起來並不簡單!

至少當你剛剛「頭」和「身體」的屬性,全定製解串器是不是太辛苦:

public static class DataPointDeserializer extends StdDeserializer<DataPoint<?>> implements ResolvableDeserializer { 
    private JsonDeserializer<Object> headerDeserializer; 
    private Map<SchemaId, JsonDeserializer<Object>> activityDeserializers; 

    public DataPointDeserializer() { 
     super(DataPoint.class); 
    } 

    @Override 
    public void resolve(DeserializationContext ctxt) throws JsonMappingException { 
     headerDeserializer = ctxt.findRootValueDeserializer(ctxt.getTypeFactory().constructType(
       DataPointHeader.class)); 
     activityDeserializers = new HashMap<>(); 
     activityDeserializers.put(new SchemaId("omh", "physical-activity"), 
       ctxt.findRootValueDeserializer(ctxt.getTypeFactory().constructType(PhysicalActivity.class))); 
    } 

    @Override 
    public DataPoint<?> deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, 
      JsonProcessingException { 
     String fieldName = p.nextFieldName(); 
     if (fieldName == null) 
      throw ctxt.wrongTokenException(p, JsonToken.FIELD_NAME, "expected 'header' and 'body' fields"); 
     if (fieldName.equals("header")) { 
      p.nextToken(); 
      DataPointHeader header = (DataPointHeader) headerDeserializer.deserialize(p, ctxt); 
      JsonDeserializer<Object> bodyDeserializer = activityDeserializers.get(header.schemaId); 
      if (bodyDeserializer == null) throw ctxt.mappingException("No mapping for schema: " + header.schemaId); 
      fieldName = p.nextFieldName(); 
      if (fieldName == null) 
       throw ctxt.wrongTokenException(p, JsonToken.FIELD_NAME, "expected 'body' field after header"); 
      p.nextToken(); 
      Measure body = (Measure) bodyDeserializer.deserialize(p, ctxt); 
      DataPoint<Measure> dataPoint = new DataPoint<>(); 
      dataPoint.header = header; 
      dataPoint.body = body; 
      return dataPoint; 
     } 
     else if (fieldName.equals("body")) { 
      p.nextToken(); 
      try (TokenBuffer tb = new TokenBuffer(p)) { 
       tb.copyCurrentStructure(p); 
       fieldName = p.nextFieldName(); 
       if (fieldName == null) 
        throw ctxt.wrongTokenException(p, JsonToken.FIELD_NAME, "expected 'header' field after body"); 
       if (!fieldName.equals("header")) 
        throw ctxt.weirdStringException(fieldName, DataPoint.class, "Unexpected field name"); 
       p.nextToken(); 
       DataPointHeader header = (DataPointHeader) headerDeserializer.deserialize(p, ctxt); 
       JsonDeserializer<Object> bodyDeserializer = activityDeserializers.get(header.schemaId); 
       if (bodyDeserializer == null) 
        throw ctxt.mappingException("No mapping for schema: " + header.schemaId); 
       JsonParser bodyParser = tb.asParser(); 
       bodyParser.nextToken(); 
       Measure body = (Measure) bodyDeserializer.deserialize(bodyParser, ctxt); 
       DataPoint<Measure> dataPoint = new DataPoint<>(); 
       dataPoint.header = header; 
       dataPoint.body = body; 
       return dataPoint; 
      } 
     } 
     else throw ctxt.weirdStringException(fieldName, DataPoint.class, "Unexpected field name"); 
    } 
} 
+0

謝謝!我會仔細檢查併發布反饋。 –

+0

我剛剛意識到它沒有檢查並消耗字段後的最終對象,我只在獨立文檔上測試它 – araqnid

2

沒有,就沒有辦法使用路徑表達式匹配屬性。這將需要訪問完整的JSON(子樹)。

隨着Jackson 2.5的推出,對於標準值(通常是Strings)的要求有輕微的放鬆,所以JSOG可能會被支持。更多的背景是在這個問題上:

https://github.com/FasterXML/jackson-databind/issues/622

但我不認爲這是遠遠不夠讓你使用標準的傑克遜式ID的解析。你可能會考慮

一件事是造物主方法是這樣的:

abstract class Measure { 
    // either constructor, or static method: 
    @JsonCreator 
    public static Measure construct(
    @JsonProperty("header") HeadOb header, // or JsonNode, Map etc 
    @JsonProperty("body") JsonNode body) { 
     // extract type info, build actual instance from body 
    } 
} 

或者,也許Converter在您使用一箇中間包裝,以頭球約束,僅機身勢必JsonNodeMap,然後從構建那裏。