我有以下格式的传入JSON数据
{
"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;
我希望杰克逊根据JSON文档中的body
将PhysicalActivity
解析为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'
错误。即使我可以,我也不认为我可以对namespace
和name
属性做出决定。
除了使用@JsonTypeResolver
?
答案 0 :(得分:2)
在杰克逊源代码中似乎有很多假设类型ID是字符串,所以我怀疑JsonTypeResolver是一种方法......但它当然看起来并不简单!
至少当你只有'header'和'body'属性时,一个完全自定义的反序列化器并不太难:
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");
}
}
答案 1 :(得分:2)
不,没有办法使用路径表达式来匹配属性。这将需要访问完整的JSON(子树)。
对于Jackson 2.5,由于类型ID的要求是标量值(通常是字符串),因此可以轻微放松,因此可以支持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
,其中您使用中间包装器来绑定标头,并且主体仅绑定到JsonNode
或Map
,然后从那里构造。