programing

특정 필드가 존재하는 경우 잭슨 다형성 역직렬화를 사용하여 서브타입으로 시리얼화할 수 있습니까?

padding 2023. 4. 3. 21:20
반응형

특정 필드가 존재하는 경우 잭슨 다형성 역직렬화를 사용하여 서브타입으로 시리얼화할 수 있습니까?

동물원 예에서 스핀을 사용하여:

public class ZooPen {
    public String type;
    public List<Animal> animals;
}

public class Animal {
    public String name;
    public int age;
}

public class Bird extends Animal {
    public double wingspan;
}

하여 구축하다Animal되지 않은 및 "wingsspan" (wingsspan) " ""Bird타입이 은 일반적으로 다음과 잭슨에서 타입이 없는 디시리얼라이제이션은 일반적으로 다음과 같습니다.

@JsonTypeInfo( 
    use = JsonTypeInfo.Id.NAME,
    include = JsonTypeInfo.As.PROPERTY,
    property = "wingspan",
    visible = true,
    defaultImpl = Animal.class
)
@JsonSubTypes({
    @Type(value = Bird.class, name = "bird")
})  
public class Animal {
    ...
}

wingspan 값은 어떤 것이든 상관없습니다.특정 일치하는 값이 없으면 Jackson은 defaultImpl 클래스로 폴백합니다.

는 아마 마마내 i i i를 사용할 수 있을 것이다.@JsonCreator:

@JsonCreator
public static Animal create(Map<String,Object> jsonMap) 
        throws JsonProcessingException {

    ObjectMapper mapper = new ObjectMapper();
    if (jsonMap.get("wingspan") == null) {
        // Construct and return animal
    } else {
        // Construct and return bird
    }
}

그 , 이 경우 이 값을 사용할 수 있는지 하지 않습니다.Animal나중에 제대로 연재될 겁니다.

서도 쓸 수 것 TypeResolver또는, 하지만, 그것은 단순히 raw json을 역직렬화하는 것 보다 더 많은 작업이 될 것 같습니다.또한.TypeResolver ★★★★★★★★★★★★★★★★★」TypeIdResolver타입 정보가 시리얼화되어 있다고 본질적으로 가정하고 있는 것 같기 때문에, 그것들을 사용하는 것은 좋지 않습니다.

타입을 지정하기 위해 라이프 사이클에 후크하면서 기본적인 잭슨 주석 처리 기능을 사용하는 나만의 방식을 구현할 수 있을까요?제가 계속 살펴봤는데, 그건 완전히 탈직렬화를 위임하는 것 같아요.TypeDeserializer또, 어떤 타입을 사용할지 판단하기 전에, 오브젝트의 일부의 시리얼화를 해제하는 문제도 있습니다.

또는 동물원의 펜이 부모 물체에 있더라도 그것을 목표로 하는 방법이 있을 수 있습니다.

다형식 핸들링으로 원하는 것을 할 수 있는 방법이 있나요?

Jackson 2.12.2에서는 "추리 기반 다형성" 기능을 사용하여 다음과 같은 목표를 달성했습니다.속성이 에 고유한 경우Bird "Subtype"(예: "Subtype"(예: "Subtype")wingspan, 은 ), 「」, 「」가 Bird 않으면 ;가 될 이다.Animal:

@JsonTypeInfo(use=Id.DEDUCTION, defaultImpl = Animal.class)
@JsonSubTypes({@Type(Bird.class)})
public class Animal {
    public String name;
    public int age;
}

연산에 기초한 다형성

차감 기반 다형성 기능은 특정 하위 유형에 고유한 속성이 있는지 여부에 따라 하위 유형을 추론합니다. 고유의으로 일의로 할 수 , 에 의해 ,defaultImpl값이 사용됩니다.

공제 기반의 다형성 기능은 Jackson 2.12에서 잭슨 데이터바인드 #43에 따라 구현되었습니다.다음 2.12 릴리즈 노트에 요약되어 있습니다.

될 수 한 또는 을 생략할 수 (「」 「」 「ID」).@JsonTypeInfo(use=DEDUCTION)필드의 존재로부터).즉, 모든 서브타입에는 포함된 고유한 필드 세트가 있기 때문에 역직렬화 중에 고유하고 확실하게 검출할 수 있습니다.

Jackson 2.12.2에서는 고유 식별 가능한 서브타입이 없을 때 예외를 발생시키지 않고 기본유형을 지정하는 이 기능이 잭슨 2.12.2에서 잭슨-databind#3055에 의해 추가되었습니다.

한 도 없는 defaultImpl적합성과 관계없이 대상 유형이어야 합니다.

추론 기반 다형성에 대한 조금 더 긴 설명은 잭슨 제작자가 쓴 "Jackson 2.12 Most Want (1/5) : 추론 기반 다형성" 기사에 나와 있습니다.

편집: 잭슨의 최신 릴리스 후보를 사용할 수 있으면 문제가 해결됩니다.https://github.com/MariusSchmidt/de.denktmit.stackoverflow/tree/main/de.denktmit.jackson에서 간단한 데모를 준비했습니다.

이 스레드(https://github.com/FasterXML/jackson-databind/issues/1627,)는 고객의 문제에 대해 설명하고 해결책을 제안합니다.Merge라는 것이 있는데, https://github.com/FasterXML/jackson-databind/pull/2813에서 볼 때 유망해 보입니다.따라서 @JsonTypeInfo(= DEUPTION 사용) 경로를 따를 수 있습니다.

Jackson의 최신 버전을 사용할 수 없는 경우 다음과 같이 처리하겠습니다.

병합 요청 백포트(OR)

  1. 잭슨을 사용하여 입력을 일반 JsonNode로 역직렬화합니다.
  2. 하나 이상의 속성이 존재하는지 https://github.com/json-path/JsonPath 체크를 사용합니다.일부 컨테이너 클래스는 클래스 유형을 고유하게 식별하는 데 필요한 모든 경로를 래핑할 수 있습니다.
  3. JsonNode를 여기에 설명된 대로 결정된 클래스에 매핑 JsonNode를 POJO로 변환

이렇게 하면 낮은 수준의 매핑 로직을 처리하지 않고도 잭슨의 모든 기능을 활용할 수 있습니다.

안부 전합니다,

마리우스

동물

import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.databind.ObjectMapper;
import de.denktmit.stackoverflow.jackson.polymorphic.deductive.Bird;
import de.denktmit.stackoverflow.jackson.polymorphic.deductive.Fish;
import org.junit.jupiter.api.Test;

import java.util.List;

import static com.fasterxml.jackson.annotation.JsonTypeInfo.Id.DEDUCTION;
import static org.assertj.core.api.Assertions.assertThat;

@JsonTypeInfo(use = DEDUCTION)
@JsonSubTypes( {@JsonSubTypes.Type(Bird.class), @JsonSubTypes.Type(Fish.class)})
public class Animal {
    private String name;
    private int age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

public class Bird extends de.denktmit.stackoverflow.jackson.polymorphic.deductive.Animal {
    private double wingspan;

    public double getWingspan() {
        return wingspan;
    }

    public void setWingspan(double wingspan) {
        this.wingspan = wingspan;
    }
}

물고기.

public class Fish extends de.denktmit.stackoverflow.jackson.polymorphic.deductive.Animal {

    private boolean freshwater;

    public boolean isFreshwater() {
        return freshwater;
    }

    public void setFreshwater(boolean freshwater) {
        this.freshwater = freshwater;
    }
}

ZooPen

public class ZooPen {

    private String type;
    private List<de.denktmit.stackoverflow.jackson.polymorphic.deductive.Animal> animals;

    public String getType() {
        return type;
    }

    public void setType(String type) {
        this.type = type;
    }

    public List<de.denktmit.stackoverflow.jackson.polymorphic.deductive.Animal> getAnimals() {
        return animals;
    }

    public void setAnimals(List<de.denktmit.stackoverflow.jackson.polymorphic.deductive.Animal> animals) {
        this.animals = animals;
    }
}

테스트

import com.fasterxml.jackson.databind.ObjectMapper;
        import de.denktmit.stackoverflow.jackson.polymorphic.deductive.Animal;
        import de.denktmit.stackoverflow.jackson.polymorphic.deductive.Bird;
        import de.denktmit.stackoverflow.jackson.polymorphic.deductive.Fish;
        import de.denktmit.stackoverflow.jackson.polymorphic.deductive.ZooPen;
        import org.junit.jupiter.api.Test;

        import static org.assertj.core.api.Assertions.assertThat;

public class DeductivePolymorphicDeserializationTest {

    private static final String birdString = "{\n" +
            "      \"name\": \"Tweety\",\n" +
            "      \"age\": 79,\n" +
            "      \"wingspan\": 2.9\n" +
            "    }";

    private static final String fishString = "{\n" +
            "      \"name\": \"Nemo\",\n" +
            "      \"age\": 16,\n" +
            "      \"freshwater\": false\n" +
            "    }";

    private static final String zooPenString = "{\n" +
            "  \"type\": \"aquaviary\",\n" +
            "  \"animals\": [\n" +
            "    {\n" +
            "      \"name\": \"Tweety\",\n" +
            "      \"age\": 79,\n" +
            "      \"wingspan\": 2.9\n" +
            "    },\n" +
            "    {\n" +
            "      \"name\": \"Nemo\",\n" +
            "      \"age\": 16,\n" +
            "      \"freshwater\": false\n" +
            "    }\n" +
            "  ]\n" +
            "}";
    private final ObjectMapper mapper = new ObjectMapper();

    @Test
    void deserializeBird() throws Exception {
        de.denktmit.stackoverflow.jackson.polymorphic.deductive.Animal animal = mapper.readValue(birdString, de.denktmit.stackoverflow.jackson.polymorphic.deductive.Animal.class);
        assertThat(animal).isInstanceOf(de.denktmit.stackoverflow.jackson.polymorphic.deductive.Bird.class);
    }

    @Test
    void deserializeFish() throws Exception {
        de.denktmit.stackoverflow.jackson.polymorphic.deductive.Animal animal = mapper.readValue(fishString, de.denktmit.stackoverflow.jackson.polymorphic.deductive.Animal.class);
        assertThat(animal).isInstanceOf(de.denktmit.stackoverflow.jackson.polymorphic.deductive.Fish.class);
    }

    @Test
    void deserialize() throws Exception {
        de.denktmit.stackoverflow.jackson.polymorphic.deductive.ZooPen zooPen = mapper.readValue(zooPenString, de.denktmit.stackoverflow.jackson.polymorphic.deductive.ZooPen.class);
        assertThat(zooPen).isInstanceOf(de.denktmit.stackoverflow.jackson.polymorphic.deductive.ZooPen.class);
    }
}

'는 않다'는가 있다고 생각합니다.@JsonCreator:

@JsonCreator
public static Animal create(Map<String,Object> jsonMap) {
    String name = (String) jsonMap.get("name");
    int age = (int) jsonMap.get("age");
    if (jsonMap.keySet().contains("wingspan")) {
        double wingspan = (double) jsonMap.get("wingspan");
        return new Bird(name, age, wingspan);
    } else {
        return new Animal(name, age);
    }
}

JsonProcessingException이 커스텀 디시리얼라이저는 내장된 Jackson 디시리얼라이저와 똑같은 이유로 실패합니다.즉, 예외 캐스팅입니다.복잡한 역직렬화에서는 코드를 이해하고 수정하는 것이 훨씬 쉬워지기 때문에 이 방법을 선호합니다.

pretius-jdl deserialization을 사용하여 목표를 달성할 수 있습니다.나는 그것이 어떻게 작동하는지 보여주기 위해 학급 계층을 조금 확장했다.다음은 샘플 코드입니다.

public class SOAnswer3 {

    @ToString @Getter @Setter
    @AllArgsConstructor @NoArgsConstructor
    public static class Animal {
        String name;
        int age;
    }

    @ToString(callSuper = true) @Getter @Setter
    @AllArgsConstructor @NoArgsConstructor
    public static class Bird extends Animal {
        double wingspan;
    }

    @ToString(callSuper = true) @Getter @Setter
    @AllArgsConstructor @NoArgsConstructor
    public static class Elephant extends Animal {
        double trunkLength;
    }

    public static void main(String[] args) {
        String json = "[{"
                + "    \"name\": \"Marty\","
                + "    \"age\": 3"
                + "},"
                + "{"
                + "    \"name\": \"Danny\","
                + "    \"age\": 7,"
                + "    \"wingspan\": 1.4159"
                + "},{"
                + "    \"name\": \"King\","
                + "    \"age\": 21,"
                + "    \"trunkLength\": 2.11"
                + "}]";
        
        // create a deserializer instance
        DynamicObjectDeserializer deserializer = new DynamicObjectDeserializer();
        
        // runtime-configure deserialization rules
        deserializer.addRule(DeserializationRuleFactory.newRule(1, // priority 
                DeserializationCriterionFactory.targetClassIsAssignableFrom(Animal.class)
                    .and((e) -> e.getJsonNode().has("wingspan")),
                DeserializationActionFactory.objectToType(Bird.class)));
        
        deserializer.addRule(DeserializationRuleFactory.newRule(1,
                DeserializationCriterionFactory.targetClassIsAssignableFrom(Animal.class)
                    .and((e) -> e.getJsonNode().has("trunkLength")),
                DeserializationActionFactory.objectToType(Elephant.class)));
        
        List<Animal> deserializedObjects = deserializer.deserializeArray(json, Animal.class);
        
        for (Animal obj : deserializedObjects) {
            System.out.println("Deserialized Class: " + obj.getClass().getSimpleName()+";\t value: "+obj.toString());
        }
    }
}

결과:

Deserialized Class: Animal;  value: SOAnswer3.Animal(name=Marty, age=3)
Deserialized Class: Bird;    value: SOAnswer3.Bird(super=SOAnswer3.Animal(name=Danny, age=7), wingspan=1.4159)
Deserialized Class: Elephant;    value: SOAnswer3.Elephant(super=SOAnswer3.Animal(name=King, age=21), trunkLength=2.11)

pretius-jdl의 Maven defendency(maven.org/jddl에서 최신 버전을 확인합니다).

<dependency>
  <groupId>com.pretius</groupId>
  <artifactId>jddl</artifactId>
  <version>1.0.0</version>
</dependency>

잭슨과 결혼하지 않았다면 FlexJSON을 사용하면 이와 비슷한 일을 할 수 있다고 생각합니다.

http://flexjson.sourceforge.net/javadoc/flexjson/JSONDeserializer.html

비슷한 작업을 수행하는 Jackson의 방법은 익숙하지 않지만 FlexJSON은 매우 성능이 뛰어나고 일반적으로 직렬화/직접적으로 사용할 수 있습니다.

안녕 Shaun 당신은 실제로 상속을 사용하는 잭슨과 함께 매우 쉽게 이 행동을 할 수 있습니다.저는 동물과 새의 시나리오를 모델링했습니다.

임플란트 내의 생성자는 동물의 올바른 인스턴스를 인스턴스화할 수 있습니다(이름과 나이가 존재하는 경우 An An Animal, 이름 나이와 날개 길이 있는 경우 Bird).이것은 Jersey와 같은 것을 사용하여 API를 통해 값을 검색하는 경우에도 동일하게 동작합니다.

@com.fasterxml.jackson.annotation.JsonSubTypes({
    @com.fasterxml.jackson.annotation.JsonSubTypes.Type(AnimalImpl.class)
})
@com.fasterxml.jackson.databind.annotation.JsonDeserialize(as = AnimalImpl.class)
public interface Animal {

    public String getName();

    public int getAge();
}

public class AnimalImpl implements Animal {

    private final String name;
    private final int age;

    public AnimalImpl(
        @JsonProperty("name") final String name,
        @JsonProperty("age") final int age
    ) {
    this.name = Objects.requireNonNull(name);
    this.age = Objects.requireNonNull(age);
    }

    @Override
    public String getName() {
    return name;
    }

    @Override
    public int getAge() {
    return age;
    }
}

@com.fasterxml.jackson.annotation.JsonSubTypes({
    @com.fasterxml.jackson.annotation.JsonSubTypes.Type(BirdImpl.class)
})
@com.fasterxml.jackson.databind.annotation.JsonDeserialize(as = BirdImpl.class)
public interface Bird extends Animal {

    public double getWingspan();
}

public class BirdImpl extends AnimalImpl implements Bird {

    private final double wingspan;

    public BirdImpl(
        @com.fasterxml.jackson.annotation.JsonProperty("name") final String name,
        @com.fasterxml.jackson.annotation.JsonProperty("age") final int age,
        @com.fasterxml.jackson.annotation.JsonProperty("wingspan") final double wingspan
    ) {
    super(name, age);
    this.wingspan = wingspan;
    }

    @Override
    public double getWingspan() {
    return wingspan;
    }
}

public class Test {

    public static void main(final String[] args) throws java.io.IOException {

    final com.fasterxml.jackson.databind.ObjectMapper objectMapper
        = new com.fasterxml.jackson.databind.ObjectMapper();

    final String animalJson = "{\"name\": \"the name\", \"age\": 42}";
    final Animal animal = objectMapper.readValue(animalJson, Animal.class);

    System.out.println(animal);

    final String birdJson = "{\"name\": \"the name\", \"age\": 42, \"wingspan\": 21}";
    final Bird bird = objectMapper.readValue(birdJson, Bird.class);

    System.out.println(bird);
    }
}

언급URL : https://stackoverflow.com/questions/16488951/can-jackson-polymorphic-deserialization-be-used-to-serialize-to-a-subtype-if-a-s

반응형