Jackson과 함께 고유 속성 존재에 따라 다형 유형 직렬화 해제
이와 같은 클래스 구조를 가진 경우:
public abstract class Parent {
private Long id;
...
}
public class SubClassA extends Parent {
private String stringA;
private Integer intA;
...
}
public class SubClassB extends Parent {
private String stringB;
private Integer intB;
...
}
''와 '비직렬화'를 할 수 있는 이 있을까요?@JsonTypeInfo에서 이 이치노
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "objectType")
에 API를 ."objectType": "SubClassA"을 Parent서브클래스
「 」를 하는 대신에, 「 」를 사용합니다.@JsonTypeInfoJackson은 서브클래스에 주석을 달아 고유 속성을 통해 다른 서브클래스와 구별하는 방법을 제공합니까?에서는 '에 'JSON이 JSON이 있다'는"stringA": ...을 로서erererer로 직렬을 하다.SubClassA가 "stringB": ...을 로서erererer로 직렬을 하다.SubClassB
에릭 길레스피의 해법을 좀 더 자세히 설명하겠습니다.당신이 부탁한 대로 잘 되어가고 있어요. 그리고 효과가 있었어요.
잭슨 2.9 사용
@JsonDeserialize(using = CustomDeserializer.class)
public abstract class BaseClass {
private String commonProp;
}
// Important to override the base class' usage of CustomDeserializer which produces an infinite loop
@JsonDeserialize(using = JsonDeserializer.None.class)
public class ClassA extends BaseClass {
private String classAProp;
}
@JsonDeserialize(using = JsonDeserializer.None.class)
public class ClassB extends BaseClass {
private String classBProp;
}
public class CustomDeserializer extends StdDeserializer<BaseClass> {
protected CustomDeserializer() {
super(BaseClass.class);
}
@Override
public BaseClass deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
TreeNode node = p.readValueAsTree();
// Select the concrete class based on the existence of a property
if (node.get("classAProp") != null) {
return p.getCodec().treeToValue(node, ClassA.class);
}
return p.getCodec().treeToValue(node, ClassB.class);
}
}
// Example usage
String json = ...
ObjectMapper mapper = ...
BaseClass instance = mapper.readValue(json, BaseClass.class);
더 화려해지고 확대해서 볼 수 있어요.CustomDeserializer Map<String, Class<?>>속성 이름을 매핑합니다(존재하는 경우).이러한 접근방식은 이 기사에 제시되어 있다.
갱신하다
Jackson 2.12.0은 사용 가능한 필드에서 다형성 서브유형을 차감하여@JsonTypeInfo(use = DEDUCTION)!
애스듀션TypeDeserializer는 필드에서 서브유형의 추론을 구현합니다.병합을 의도하지 않은 POC로서 Tere는 cut'n'paste 코드 등이지만, 기능적인 PR은 흥미로 쓴 것에 대한 논의의 가장 좋은 근거라고 생각했습니다.
이 기능은 등록 시 각 서브타입의 가능한 필드 세트를 모두 핑거프린트함으로써 동작합니다.탈직렬화 시, 사용 가능한 필드는 한 후보만 남을 때까지 해당 지문과 비교됩니다.특히 직계자녀 필드 이름만 보고 직계자녀 값은 기존 메커니즘에 의해 다루어지기 때문에 심층 분석은 잭슨 소관의 일부가 아닌 훨씬 더 강력한 ML 작업입니다.
덧붙여서, (현재 종료된) Github의 문제가 여기에 있습니다.https://github.com/FasterXML/jackson-databind/issues/1627
...@JsonTypeInfo ★★★★★★★★★★★★★★★★★」@JsonSubTypes사용되어야 하는데 문서를 통해 확인해보니 당신이 설명한 것과 일치하는 부동산이 하나도 없는 것 같아요.
해서 맞춤 할 수 .@JsonSubTypes 것을 할 수 있습니다.와 디시리얼라이저@JsonSubTypes는 기본 클래스로 제공되며, 역직렬라이저는 "name" 값을 사용하여 속성이 존재하는지 확인하고, 존재하는 경우 JSON을 "value" 속성에 제공된 클래스로 역직렬화합니다.그러면 클래스는 다음과 같습니다.
@JsonDeserialize(using = PropertyPresentDeserializer.class)
@JsonSubTypes({
@Type(name = "stringA", value = SubClassA.class),
@Type(name = "stringB", value = SubClassB.class)
})
public abstract class Parent {
private Long id;
...
}
public class SubClassA extends Parent {
private String stringA;
private Integer intA;
...
}
public class SubClassB extends Parent {
private String stringB;
private Integer intB;
...
}
다른 사람들이 지적한 바와 같이, 어떻게 작동해야 하는지에 대한 합의가 이루어지지 않아 구현되지 않았습니다.
클래스 Foo, Bar 및 그 부모 FooBar 솔루션이 있는 경우 JSON은 다음과 같습니다.
{
"foo":<value>
}
또는
{
"bar":<value>
}
하지만 당신이 이 모든 것을 알게 되면 어떤 일이 일어나는지
{
"foo":<value>,
"bar":<value>
}
언뜻 보면 마지막 예는 400개의 Bad Request의 명백한 사례처럼 보이지만 실제로는 다양한 접근법이 있습니다.
- 400 Bad Request로 처리
- 유형/필드별 우선순위(예를 들어 필드 오류가 있는 경우 다른 필드 foo보다 우선 순위가 높음)
- 더 복잡한 경우 2개입니다.
대부분의 경우에 유효하고 기존의 잭슨 인프라스트럭처를 최대한 활용하려고 하는 현재의 솔루션은 다음과 같습니다(계층별로 1개의 디시리얼라이저만 있으면 됩니다).
public class PresentPropertyPolymorphicDeserializer<T> extends StdDeserializer<T> {
private final Map<String, Class<?>> propertyNameToType;
public PresentPropertyPolymorphicDeserializer(Class<T> vc) {
super(vc);
this.propertyNameToType = Arrays.stream(vc.getAnnotation(JsonSubTypes.class).value())
.collect(Collectors.toMap(Type::name, Type::value,
(a, b) -> a, LinkedHashMap::new)); // LinkedHashMap to support precedence case by definition order
}
@Override
public T deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
ObjectMapper objectMapper = (ObjectMapper) p.getCodec();
ObjectNode object = objectMapper.readTree(p);
for (String propertyName : propertyNameToType.keySet()) {
if (object.has(propertyName)) {
return deserialize(objectMapper, propertyName, object);
}
}
throw new IllegalArgumentException("could not infer to which class to deserialize " + object);
}
@SuppressWarnings("unchecked")
private T deserialize(ObjectMapper objectMapper,
String propertyName,
ObjectNode object) throws IOException {
return (T) objectMapper.treeToValue(object, propertyNameToType.get(propertyName));
}
}
사용 예:
@JsonSubTypes({
@JsonSubTypes.Type(value = Foo.class, name = "foo"),
@JsonSubTypes.Type(value = Bar.class, name = "bar"),
})
interface FooBar {
}
@AllArgsConstructor(onConstructor_ = @JsonCreator)
@Value
static class Foo implements FooBar {
private final String foo;
}
@AllArgsConstructor(onConstructor_ = @JsonCreator)
@Value
static class Bar implements FooBar {
private final String bar;
}
잭슨 설정
SimpleModule module = new SimpleModule();
module.addDeserializer(FooBar.class, new PresentPropertyPolymorphicDeserializer<>(FooBar.class));
objectMapper.registerModule(module);
또는 Spring Boot를 사용하는 경우:
@JsonComponent
public class FooBarDeserializer extends PresentPropertyPolymorphicDeserializer<FooBar> {
public FooBarDeserializer() {
super(FooBar.class);
}
}
테스트:
@Test
void shouldDeserializeFoo() throws IOException {
// given
var json = "{\"foo\":\"foo\"}";
// when
var actual = objectMapper.readValue(json, FooBar.class);
// then
then(actual).isEqualTo(new Foo("foo"));
}
@Test
void shouldDeserializeBar() throws IOException {
// given
var json = "{\"bar\":\"bar\"}";
// when
var actual = objectMapper.readValue(json, FooBar.class);
// then
then(actual).isEqualTo(new Bar("bar"));
}
@Test
void shouldDeserializeUsingAnnotationDefinitionPrecedenceOrder() throws IOException {
// given
var json = "{\"bar\":\"\", \"foo\": \"foo\"}";
// when
var actual = objectMapper.readValue(json, FooBar.class);
// then
then(actual).isEqualTo(new Foo("foo"));
}
편집: 이 프로젝트에서는 이 케이스에 대한 지원을 추가했습니다.
EDIT (2021-07-15) -- 구식, 현황에 대한 M. Justin의 답변을 참조하십시오.
(원래 답변은 다음과 같습니다)
아니요. 이러한 기능은 "유형 추론" 또는 "암시형"이라고 불릴 수 있지만, 이 기능이 어떻게 작동해야 하는지에 대한 실행 가능한 일반적인 제안을 아직 내놓지 못했습니다.특정 사례에 대한 특정 솔루션을 지원하는 방법을 생각하는 것은 쉽지만 일반적인 해결책을 찾는 것은 더 어렵습니다.
이 기능은 "추리 기반 다형성"을 사용하여 Jackson 2.12에 추가되었습니다.당신의 경우에 적용하기 위해서는@JsonTypeInfo(use=Id.DEDUCTION)에서 @JsonSubTypes:
@JsonTypeInfo(use=Id.DEDUCTION)
@JsonSubTypes({@Type(SubClassA.class), @Type(SubClassB.class)})
public abstract class Parent {
private Long id;
...
}
이 기능은 jackson-databind #43마다 구현되었습니다.다음 2.12 릴리즈 노트에 요약되어 있습니다.
될 수 한 또는 을 생략할 수 (「」 「」 「ID」).
@JsonTypeInfo(use=DEDUCTION)필드의 존재로부터).즉, 모든 서브타입에는 포함된 고유한 필드 세트가 있기 때문에 역직렬화 중에 고유하고 확실하게 검출할 수 있습니다.
좀 더 긴 설명은 잭슨 제작자가 쓴 "Jackson 2.12 Most Wanted (1/5) : Deduation-Based Polymorphism" 기사에 나와 있습니다.
오래된 구조를 유지할 필요가 있는 앱이기 때문에 데이터를 변경하지 않고 다형성을 지원할 수 있는 방법을 찾았습니다.제가 하는 일은 다음과 같습니다.
- JsonDeserializer 확장
Tree and read 필드로 변환한 다음 Subclass 개체를 반환합니다.
@Override public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { JsonNode jsonNode = p.readValueAsTree(); Iterator<Map.Entry<String, JsonNode>> ite = jsonNode.fields(); boolean isSubclass = false; while (ite.hasNext()) { Map.Entry<String, JsonNode> entry = ite.next(); // **Check if it contains field name unique to subclass** if (entry.getKey().equalsIgnoreCase("Field-Unique-to-Subclass")) { isSubclass = true; break; } } if (isSubclass) { return mapper.treeToValue(jsonNode, SubClass.class); } else { // process other classes } }
다형성을 처리하려면 모델에 바인딩되거나 다양한 사용자 지정 역직렬화기를 사용하여 많은 코드가 필요합니다.JSON Dynamic Diserialization Library의 공동 저자입니다.이 라이브러리는 모델 독립형 json deserialization Library는 JSON Dynamic Diserialization Library입니다.OP 문제의 해결 방법은 다음과 같습니다.규칙은 매우 간략하게 선언됩니다.
public class SOAnswer1 {
@ToString @Getter @Setter
@AllArgsConstructor @NoArgsConstructor
public static abstract class Parent {
private Long id;
}
@ToString(callSuper = true) @Getter @Setter
@AllArgsConstructor @NoArgsConstructor
public static class SubClassA extends Parent {
private String stringA;
private Integer intA;
}
@ToString(callSuper = true) @Getter @Setter
@AllArgsConstructor @NoArgsConstructor
public static class SubClassB extends Parent {
private String stringB;
private Integer intB;
}
public static void main(String[] args) {
String json = "[{"
+ " \"id\": 151243,"
+ " \"stringA\": \"my special string\","
+ " \"intA\": 1337"
+ "},"
+ "{"
+ " \"id\": 734561,"
+ " \"stringB\": \"use the Force\","
+ " \"intB\": 451"
+ "}]";
// create a deserializer instance
DynamicObjectDeserializer deserializer = new DynamicObjectDeserializer();
// runtime-configure deserialization rules
deserializer.addRule(DeserializationRuleFactory.newRule(1,
(e) -> e.getJsonNode().has("stringA"),
DeserializationActionFactory.objectToType(SubClassA.class)));
deserializer.addRule(DeserializationRuleFactory.newRule(1,
(e) -> e.getJsonNode().has("stringB"),
DeserializationActionFactory.objectToType(SubClassB.class)));
List<Parent> deserializedObjects = deserializer.deserializeArray(json, Parent.class);
for (Parent obj : deserializedObjects) {
System.out.println("Deserialized Class: " + obj.getClass().getSimpleName()+";\t value: "+obj.toString());
}
}
}
출력:
Deserialized Class: SubClassA; value: SOAnswer1.SubClassA(super=SOAnswer1.Parent(id=151243), stringA=my special string, intA=1337)
Deserialized Class: SubClassB; value: SOAnswer1.SubClassB(super=SOAnswer1.Parent(id=734561), stringB=use the Force, intB=451)
pretius-jdl의 Maven defendency(maven.org/jddl에서 최신 버전을 확인합니다).
<dependency>
<groupId>com.pretius</groupId>
<artifactId>jddl</artifactId>
<version>1.0.0</version>
</dependency>
언급URL : https://stackoverflow.com/questions/24263904/deserializing-polymorphic-types-with-jackson-based-on-the-presence-of-a-unique-p
'programing' 카테고리의 다른 글
| Wordpress 4.2.2 업데이트 실패 wpdb-> 삽입 (0) | 2023.04.04 |
|---|---|
| Wordpress ACF - 날짜 형식 (0) | 2023.04.04 |
| JSONAray 및 JSONObject를 사용하여 포어치 (0) | 2023.04.04 |
| 각도에서의 특성 변경 구독JS (0) | 2023.04.04 |
| 문자열이 jq의 유효한 JSON인지 확인합니다. (0) | 2023.04.04 |