Java 将对象转换为抽象对象

Java convert object into abstract object

我正在使用 SpringBoot with Java 1.8.

我有两个对象,我想将一个对象深复制到另一个对象。

基本结构

QuoteRequestDTO -> TravelRequirementDTO -> ItineraryDTO -> ServiceDTO

QuoteRequest -> TravelRequirement -> Itinerary -> Service

注意:实体对象来自我无法更改的外部库。我可以更改 DTO 对象。

比如我想复制一个DTO到一个Entity

DTO

public class QuoteRequestDTO {    
    protected TravelRequirementDTO travel;
    ...

public class TravelRequirementDTO {
    protected ItineraryDTO required;
    ...

public class ItineraryDTO extends PayableDTO {
    protected List<ServiceDTO> service;
    ...

public class ServiceDTO extends PayableDTO {
    ...

实体

public class QuoteRequest {
    protected TravelRequirement travel;
    ...

public class TravelRequirement implements Serializable {
    private static final long serialVersionUID = 1L;
    protected Itinerary required;
    ...

public class Itinerary extends Payable implements Serializable {
    private static final long serialVersionUID = 1L;
    protected List<Service> service;
    ...

public abstract class Service extends Payable implements Serializable {
    private static final long serialVersionUID = 1L;
    ...

复制实用程序

我试过以下方法:

import com.fasterxml.jackson.databind.ObjectMapper;

public class CopyUtils {

    public static <T>T deepCopy(Object sourceObject, T targetObject) {
        ObjectMapper mapper = getJacksonObjectMapper();
        T targetBean = (T) mapper.convertValue(sourceObject, targetObject.getClass());
        return targetBean;
    }

    private static ObjectMapper getJacksonObjectMapper() {
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.findAndRegisterModules();
        objectMapper.configure(com.fasterxml.jackson.databind.SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
        objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
        objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
        return objectMapper;
    }
}

用法:

public void getQuote(QuoteRequestDTO quoteRequestDTO) {
    QuoteRequest quoteRequest = new QuoteRequest();
    quoteRequest = CopyUtils.deepCopy(quoteRequestDTO, quoteRequest);

错误

出现以下错误:

Cannot construct instance of com.mycompany.transit._2008a.Service (no Creators, like default constructor, exist): abstract types either need to be mapped to concrete types, have custom deserializer, or contain additional type information at [Source: UNKNOWN; byte offset: #UNKNOWN] (through reference chain: com.mycompany.transit._2008a.availability.QuoteRequest["travel"]->com.mycompany.transit._2008a.TravelRequirement["required"]->com.mycompany.transit._2008a.Itinerary["service"]->java.util.ArrayList[0])

当我将 ServiceDTO 更改为 抽象 class:

public abstract class ServiceDTO extends PayableDTO implements Serializable {
    private static final long serialVersionUID = 1L;

出现以下错误:

com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of com.mycompany.restosgi.dto.transit.ServiceDTO (no Creators, like default constructor, exist): abstract types either need to be mapped to concrete types, have custom deserializer, or contain additional type information at [Source: (org.springframework.util.StreamUtils$NonClosingInputStream); line: 13, column: 13] (through reference chain: com.mycompany.restosgi.dto.transit.availability.QuoteRequestDTO["travel"]->com.mycompany.restosgi.dto.transit.TravelRequirementDTO["required"]->com.mycompany.restosgi.dto.transit.ItineraryDTO["service"]->java.util.ArrayList[0])

问题

有没有一种方法可以编写一个通用的实用方法来将对象深度复制到另一个具有抽象对象的对象?

可能的解决方案

有没有办法添加一个转换器来创建相关的具体 class(实现)?

Service 需要是一个实现,例如 TransitService

例如

public class TransitService extends Service implements Serializable {
    private static final long serialVersionUID = 1L;

可能的解决方案

根据下面 Delta George 的建议,我正在尝试以下操作:

public class ItineraryDTO extends PayableDTO {
    protected List<ServiceDTO> service;

    @JsonAnySetter
    public void serService(String key, ArrayNode array) {
        service = new ArrayList<>();
        array.forEach(json -> service.add(toService(json)));
    }

    private ServiceDTO toService(JsonNode json) {
        if (json.has("some unique property of flight")) {
            return new ObjectMapper().convertValue(json, FlightDTO.class);
        } else if (json.has("some unique property of transit")) {
            return new ObjectMapper().convertValue(json, TransitServiceDTO.class);
        } else return null;
    }

    ...

但是,我无法让它调用 serService 方法。我想我需要添加一些 Spring 配置才能这样做?

此外,如果我对 ServiceDTO 进行抽象,则会出现以下错误。

public abstract class ServiceDTO extends PayableDTO {

com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of com.clubtravel.restosgi.dto.transit.ServiceDTO (no Creators, like default constructor, exist): abstract types either need to be mapped to concrete types, have custom deserializer, or contain additional type information at [Source: (org.springframework.util.StreamUtils$NonClosingInputStream); line: 13, column: 13] (through reference chain: com.clubtravel.restosgi.dto.transit.availability.QuoteRequestDTO["travel"]->com.clubtravel.restosgi.dto.transit.TravelRequirementDTO["required"]->com.clubtravel.restosgi.dto.transit.ItineraryDTO["service"]->java.util.ArrayList[0]) at com.fasterxml.jackson.databind.exc.InvalidDefinitionException.from(InvalidDefinitionException.java:67) ~[jackson-databind-2.13.2.1.jar:2.13.2.1]

使用 Jackson 的@JsonAnySetter 将传入对象拦截为 JSON 树数组并根据每个对象的结构将每个对象转换为具体的 POJO 的概念实现:

@Data
@NoArgsConstructor
static abstract class Person {
}

@Data
@NoArgsConstructor
static class PersonA extends Person {
    String a;
}

@Data
@NoArgsConstructor
static class PersonB extends Person {
    String b;
}

@NoArgsConstructor
@ToString
static class People {
    List<Person> team;

    @JsonAnySetter
    public void setTeam(String key, ArrayNode array) {
        team = new ArrayList<>();
        array.forEach(json -> team.add(toPerson(json)));
    }

    private Person toPerson(JsonNode json) {
        if (json.has("a")) {
            return new ObjectMapper().convertValue(json, PersonA.class);
        } else if (json.has("b")) {
            return new ObjectMapper().convertValue(json, PersonB.class);
        } else return null;
    }
}

public static void main(String[] args) throws JsonProcessingException {
    String json = "{\"team\": [{\"a\": 123}, {\"b\": 45}]}";

    People people = new ObjectMapper().readValue(json, People.class);
    System.out.println(people);

    // Prints: People(team=[PersonA(a=123), PersonB(b=45)])
}

// back to the OP's data model

public static class QuoteRequest {
    protected TravelRequirement travel;

    public TravelRequirement getTravel() {
        return travel;
    }

    public void setTravel(TravelRequirement travel) {
        this.travel = travel;
    }
}

public static class TravelRequirement implements Serializable {
    private static final long serialVersionUID = 1L;
    protected Itinerary required;

    public Itinerary getRequired() {
        return required;
    }

    public void setRequired(Itinerary required) {
        this.required = required;
    }
}

public static class Itinerary extends Payable implements Serializable {
    private static final long serialVersionUID = 1L;
    protected List<Service> service;

    public void service(List<Service> service) {
        this.service = service;
    }

    @JsonAnyGetter
    public Map<String, Object> getService() {
        return Map.of("service", service);
    }

    @JsonAnySetter
    public void setService(String key, ArrayNode array) {
        service = new ArrayList<>();
        array.forEach(json -> service.add(toService(json)));
    }

    private Service toService(JsonNode json) {
        return getJacksonObjectMapper().convertValue(json, TransitService.class);
    }
}

public static abstract class Service extends Payable implements Serializable {
    private static final long serialVersionUID = 1L;
}

public static class TransitService extends Service implements Serializable {
    private static final long serialVersionUID = 1L;
}

public static class Payable implements Serializable {
    private static final long serialVersionUID = 1L;
}

private static ObjectMapper getJacksonObjectMapper() {
    ObjectMapper objectMapper = new ObjectMapper();
    objectMapper.findAndRegisterModules();
    objectMapper.configure(com.fasterxml.jackson.databind.SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
    objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
    objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
    objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
    return objectMapper;
}

public static void main(String[] args) throws JsonProcessingException {
    QuoteRequest qr = new QuoteRequest();
    TravelRequirement tr = new TravelRequirement();
    Itinerary i = new Itinerary();
    i.service(List.of(new TransitService()));
    tr.setRequired(i);
    qr.setTravel(tr);

    ObjectMapper mapper = getJacksonObjectMapper();
    QuoteRequest qr2 = mapper.convertValue(qr, QuoteRequest.class);
    System.out.println(qr2);
}