使用 Jackson @JsonDeserializer 将一个自定义 Java 字段映射到多个 JSON 字段
Mapping one custom Java field to many JSON fields using Jackson @JsonDeserializer
我有一个 java class 代表 JSON 使用 Jackson。除了一个例外,所有字段都可以完全不使用注释进行翻译。一对一,简单的翻译(尽管其中一些是嵌套的 POJO)。
@Data
@AllArgsConstructor
@NoArgsConstructor
public class MyPojo {
private String someString;
private AnotherPojo someOtherPojo;
//The problem child:
private Object value;
}
字段 value
是此规则的一个例外,它可以表示匹配 value*
的任何 JSON 字段,其中 *
是不定长度的通配符。这意味着 JSON 中的 valueString
或 valueReference
将被分配给该字段,并断言只能存在一个。
{
"someString": "asdasdadasdsa",
"someOtherPojo": {
"someOtherProperty": "whatever"
},
"valueCodeableConcept": {
"text": "text value",
"coding": [
{
"code": "my-code"
}
]
}
}
在顶层使用自定义反序列化器 class,我可以从根节点(下例中的 baseNode
)中抓取所有以 value
开头的字段并适当地设置值字段。效果很好!但是,在这样做的过程中,我现在必须在我的反序列化器中手动设置此 MyPojo
class 中的所有其他字段,并且我必须将此反序列化器的自定义副本放在每个使用类似字段的 POJO 上value*
。
private Object parseValueX(JsonNode baseNode, DeserializationContext context) throws IOException {
//Find the concrete implementation referred to by the value[x] field
Set<String> concreteNames = new HashSet<>();
baseNode.fieldNames().forEachRemaining(name -> {
if (name.startsWith("value")) {
concreteNames.add(name);
}});
if (concreteNames.isEmpty()) {
return null;
}
if (concreteNames.size() > 1) {
throw JsonMappingException.from(context, "The field value[x] must have no more than one concrete " +
"implementation, ex: valueCode, valueCodeableConcept, valueReference");
}
String concreteName = concreteNames.stream().findFirst().orElseThrow(() -> new RuntimeException(""));
JsonNode jsonSource = baseNode.get(concreteName);
//...deserialize from jsonSource, solved, but not relevant to question...
}
为了使其适用于任何 POJO 上的任何 value*
属性,我尝试将反序列化器移动到 POJO 中的 value
属性(而它位于顶层现在的资源)。第一个缺陷是反序列化器甚至不会被调用,除非 JSON 属性 完全匹配 value
。我真正需要的是将整个父 JSON 资源传递给该字段特定的反序列化器,这样我就可以找到匹配的字段并分配它——或者——我需要能够拥有反序列化器on MyPojo
only 分配一个字段 value
并允许自动反序列化处理其他字段。我该怎么做?
对于那些对我的动机感到好奇的人,我正在实施 HL7 FHIR 规范,它指定了称为 value[x] 的通用属性(这是一个示例:https://www.hl7.org/fhir/extensibility.html#Extension),其中 [x] 成为资源的类型.
我认为最适合您的问题是 @JsonAnySetter。此方法注释告诉 Jackson 将未知属性路由到它。 arg(在你的例子中)是一个包含未知 属性 的 json 树的映射。如果我正确理解您的代码,值 属性 的名称包含目标 Pojo 的 class 名称。所以一旦你有了一个 class 名字,你就可以告诉 Jackson 如何 "deserialize" 映射到目标实例 class.
这是一个基于问题代码的示例
public class MyPojo {
public String someString; // made properties into public for this example...
public AnotherPojo someOtherPojo;
public Object value;
@JsonAnySetter
public void setValue(String name, Object value) {
System.out.println(name + " " + value.getClass());
System.out.println(value);
// basic validation
if (name.startsWith("value") && value instanceof Map) {
String className = "com.company." + name.substring("value".length());
System.out.println(name + " " + value.getClass() + " " + className);
System.out.println(value);
try {
// nice of Jackson to be able to deserialize Map into Pojo :)
ObjectMapper mapper = new ObjectMapper();
this.value = mapper.convertValue(value, Class.forName(className));
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(this.value + " " + this.value.getClass());
}
}
}
public class AnotherPojo {
public String someOtherProperty;
}
public class CodeableConcept {
public String text;
public Code[] coding;
}
public class Code {
public String code;
}
我有一个 java class 代表 JSON 使用 Jackson。除了一个例外,所有字段都可以完全不使用注释进行翻译。一对一,简单的翻译(尽管其中一些是嵌套的 POJO)。
@Data
@AllArgsConstructor
@NoArgsConstructor
public class MyPojo {
private String someString;
private AnotherPojo someOtherPojo;
//The problem child:
private Object value;
}
字段 value
是此规则的一个例外,它可以表示匹配 value*
的任何 JSON 字段,其中 *
是不定长度的通配符。这意味着 JSON 中的 valueString
或 valueReference
将被分配给该字段,并断言只能存在一个。
{
"someString": "asdasdadasdsa",
"someOtherPojo": {
"someOtherProperty": "whatever"
},
"valueCodeableConcept": {
"text": "text value",
"coding": [
{
"code": "my-code"
}
]
}
}
在顶层使用自定义反序列化器 class,我可以从根节点(下例中的 baseNode
)中抓取所有以 value
开头的字段并适当地设置值字段。效果很好!但是,在这样做的过程中,我现在必须在我的反序列化器中手动设置此 MyPojo
class 中的所有其他字段,并且我必须将此反序列化器的自定义副本放在每个使用类似字段的 POJO 上value*
。
private Object parseValueX(JsonNode baseNode, DeserializationContext context) throws IOException {
//Find the concrete implementation referred to by the value[x] field
Set<String> concreteNames = new HashSet<>();
baseNode.fieldNames().forEachRemaining(name -> {
if (name.startsWith("value")) {
concreteNames.add(name);
}});
if (concreteNames.isEmpty()) {
return null;
}
if (concreteNames.size() > 1) {
throw JsonMappingException.from(context, "The field value[x] must have no more than one concrete " +
"implementation, ex: valueCode, valueCodeableConcept, valueReference");
}
String concreteName = concreteNames.stream().findFirst().orElseThrow(() -> new RuntimeException(""));
JsonNode jsonSource = baseNode.get(concreteName);
//...deserialize from jsonSource, solved, but not relevant to question...
}
为了使其适用于任何 POJO 上的任何 value*
属性,我尝试将反序列化器移动到 POJO 中的 value
属性(而它位于顶层现在的资源)。第一个缺陷是反序列化器甚至不会被调用,除非 JSON 属性 完全匹配 value
。我真正需要的是将整个父 JSON 资源传递给该字段特定的反序列化器,这样我就可以找到匹配的字段并分配它——或者——我需要能够拥有反序列化器on MyPojo
only 分配一个字段 value
并允许自动反序列化处理其他字段。我该怎么做?
对于那些对我的动机感到好奇的人,我正在实施 HL7 FHIR 规范,它指定了称为 value[x] 的通用属性(这是一个示例:https://www.hl7.org/fhir/extensibility.html#Extension),其中 [x] 成为资源的类型.
我认为最适合您的问题是 @JsonAnySetter。此方法注释告诉 Jackson 将未知属性路由到它。 arg(在你的例子中)是一个包含未知 属性 的 json 树的映射。如果我正确理解您的代码,值 属性 的名称包含目标 Pojo 的 class 名称。所以一旦你有了一个 class 名字,你就可以告诉 Jackson 如何 "deserialize" 映射到目标实例 class.
这是一个基于问题代码的示例
public class MyPojo {
public String someString; // made properties into public for this example...
public AnotherPojo someOtherPojo;
public Object value;
@JsonAnySetter
public void setValue(String name, Object value) {
System.out.println(name + " " + value.getClass());
System.out.println(value);
// basic validation
if (name.startsWith("value") && value instanceof Map) {
String className = "com.company." + name.substring("value".length());
System.out.println(name + " " + value.getClass() + " " + className);
System.out.println(value);
try {
// nice of Jackson to be able to deserialize Map into Pojo :)
ObjectMapper mapper = new ObjectMapper();
this.value = mapper.convertValue(value, Class.forName(className));
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(this.value + " " + this.value.getClass());
}
}
}
public class AnotherPojo {
public String someOtherProperty;
}
public class CodeableConcept {
public String text;
public Code[] coding;
}
public class Code {
public String code;
}