Java @JsonTypeResolver是使用多个属性进行解析的唯一选项吗?

Java @JsonTypeResolver是使用多个属性进行解析的唯一选项吗?,java,json,jackson,Java,Json,Jackson,我有以下格式的传入JSON数据 { "header": { "schema_id": { "namespace": "omh", "name": "physical-activity", }, }, "body": { "activity_name": "walking", "distance": { "value": 1.5,

我有以下格式的传入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;
我希望Jackson根据JSON文档中的
schema\u id
body
解析为
PhysicalActivity
类型,例如伪代码

if schema_id.namespace == 'omh' && schema_id.name == 'physical-activity'
     then return PhysicalActivity.class
我曾尝试使用
@JsonTypeIdResolver
执行此操作,但如果我尝试使用
@JsonTypeInfo
导航到
header.schema\u id.name
,例如

@JsonTypeInfo(use = JsonTypeInfo.Id.CUSTOM,
        include = JsonTypeInfo.As.EXTERNAL_PROPERTY,
        property = "header.schema_id.name")
@JsonTypeIdResolver(DataPointTypeIdResolver.class)
public abstract class Measure {
我得到一个
缺少的属性:“header.schema\u id.name”
错误。即使我可以,我也不认为我可以对
名称空间
名称
属性做出决定


除了用
@jsontypesolver
从头开始构建之外,还有什么明智的方法可以做到这一点吗?

在Jackson源代码中,似乎有很多假设类型ID是字符串,所以我怀疑JSontypesolver是一种方法。。。当然,这似乎并不简单

至少当您只有“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");
    }
}
public静态类DataPointDeserializer扩展StdDeserializer反序列化(JsonParser p,DeserializationContext ctxt)抛出IOException,
JsonProcessingException{
字符串fieldName=p.nextFieldName();
if(fieldName==null)
抛出ctxt.ErrorTokenException(p,JsonToken.FIELD_NAME,“应为'header'和'body'字段”);
if(fieldName.equals(“标头”)){
p、 nextToken();
DataPointHeader=(DataPointHeader)headerDeserializer.deserialize(p,ctxt);
JsonDeserializer bodyDeserializer=activityDeserializers.get(header.schemaId);
if(bodyDeserializer==null)抛出ctxt.mappingException(“架构没有映射:”+header.schemaId);
fieldName=p.nextFieldName();
if(fieldName==null)
抛出ctxt.ErrorTokenException(p,JsonToken.FIELD_NAME,“头后应为'body'字段”);
p、 nextToken();
度量体=(度量)体反序列化器。反序列化(p,ctxt);
数据点数据点=新数据点();
dataPoint.header=头;
dataPoint.body=body;
返回数据点;
}
else if(fieldName.equals(“body”)){
p、 nextToken();
try(TokenBuffer tb=新的TokenBuffer(p)){
tb.copyCurrentStructure(p);
fieldName=p.nextFieldName();
if(fieldName==null)
抛出ctxt.ErrorTokenException(p,JsonToken.FIELD_NAME,“正文后应为'header'字段”);
如果(!fieldName.equals(“标头”))
抛出ctxt.weirdStringException(fieldName,DataPoint.class,“意外字段名”);
p、 nextToken();
DataPointHeader=(DataPointHeader)headerDeserializer.deserialize(p,ctxt);
JsonDeserializer bodyDeserializer=activityDeserializers.get(header.schemaId);
if(bodyDeserializer==null)
抛出ctxt.mappingException(“架构:+header.schemaId没有映射”);
JsonParser-bodyParser=tb.aspaser();
bodyParser.nextToken();
Measure body=(Measure)bodyDeserializer.deserialize(bodyParser,ctxt);
数据点数据点=新数据点();
dataPoint.header=头;
dataPoint.body=body;
返回数据点;
}
}
else抛出ctxt.weirdStringException(fieldName,DataPoint.class,“意外字段名”);
}
}

否,无法使用路径表达式匹配属性。这需要访问完整的JSON(子树)

对于Jackson 2.5,类型ID是标量值(通常是字符串)的要求略有放宽,因此这可能得到支持。有关这一问题的更多背景信息:

但我认为这还不足以让你使用标准的Jackson类型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
,您使用一个中间包装器来绑定头,而body只绑定到
JsonNode
Map
,然后从那里进行构造。

谢谢!我会仔细检查并发布反馈。我刚刚意识到它不会在字段之后检查和使用最终对象,我只在独立文档上测试了它
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
     }
}