有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

具有变量Json响应对象和Json属性的java Jackson泛型

基于VizGhar的答案“Jackson Generics with variable JsonProperty”,我尝试使用WebClient来使用API响应,该响应在json响应中包含一个通用对象:

{
"meta": { 
    "RID": "abc9f-defgh-hj78k-lkm9n",
    "QID": "abc9f-defgh-hj78k-lkm9n" },
"data": { 
        "Inquiry": {
            "multiCurrency": [{"TaxStat": "Y", "TaxAmt": 0}],
            "Type": "Tax",
            "TaxFreq": { 
                "weekDay": 0,
                "startDay": 0 
            },
            "TaxRegion": "Tx" 
        } 
    }
}

其中,“查询”的类型是泛型的,即“数据”是泛型响应对象的包装,在本例中为“查询”,但它可能会更改

调查。爪哇:

public class Inquiry {
    @JsonProperty("multiCurrency")
    private List<MultiCurrencyInq> multiCurrency;

    @JsonProperty("Type")
    private String Type;

    @JsonProperty("TaxFreq")
    private TaxFreq taxFreq;

    @JsonProperty("TaxRegion")
    private String TaxRegion;

    // Getters Setters Constructors
}

多币种。爪哇:

public class MultiCurrencyInq {

    @JsonProperty("TaxStat")
    private String TaxStat;

    @JsonProperty("TaxAmt")
    private int TaxAmt;

    // Getters Setters Constructors
}

TaxFreq。爪哇:

public class TaxFreq {

    @JsonProperty("weekDay")
    private int weekDay;

    @JsonProperty("startDay")
    private int startDay;

    // Getters Setters Constructors
}

我的回答。java看起来像这样:

public class Response<T>{
    private Meta meta;
    private Data<T> data;
    // Getters Setters Constructors
}

梅塔。爪哇:

public class Meta{
    private String RID;
    private String QID;
    // Getters Setters Constructors
}

数据。爪哇:

public class Data<T> {
    // property name, that will be changed
    @JsonProperty(DataNamingStrategy.DATA_FIELD)
    private T data;
    // Getters Setters Constructors
}

我的控制器:

@RestController
public class InquiryController {

    @Autowired private WebClient webClient;

    @GetMapping("/inquiry") public Response<Inquiry> getInquiryApiResponse() {
        ResponseEntity<String> response = webClient.get()
                .uri("http://my.org.com/clientId/inquiry")
                .retrieve()
                .toEntity(String.class)
                .block();

        ObjectMapper mapper = new ObjectMapper();
        mapper.setPropertyNamingStrategy(new DataNamingStrategy("Inquiry")); 
        JavaType type = mapper.getTypeFactory()
                .constructParametricType(Response.class, Inquiry.class);

        Response<Inquiry> res = mapper.readValue(response.getBody(), type);
        return res;
    }
}

数据命名策略。爪哇:

public class DataNamingStrategy extends PropertyNamingStrategy{

    // used by other classes (this will be default field name that should be changed)
    public static final String DATA_FIELD = "variable:data";
    private String fieldName;

    public DataNamingStrategy(String fieldName) {
        this.fieldName = fieldName;
    }

    // use this to change field name (format "variable":"value") not needed in my case
    @Override
    public String nameForField(MapperConfig<?> config, AnnotatedField field,
            String defaultName) {
        return (defaultName.equals(DATA_FIELD))?
            fieldName :
            super.nameForField(config, field, defaultName);
    }

    // use this to change setter method field name (JSON -> Object with format "variable":{})
    @Override
    public String nameForSetterMethod(MapperConfig<?> config,
            AnnotatedMethod method, String defaultName) {
        return (defaultName.equals(DATA_FIELD))?
            fieldName :
            super.nameForGetterMethod(config, method, defaultName);
    }

    // use this to change getter method field name (Object -> JSON with format "variable":{})
    // should be same as nameForSetterMethod
    @Override
    public String nameForGetterMethod(MapperConfig<?> config,
            AnnotatedMethod method, String defaultName) {
        return nameForSetterMethod(config, method, defaultName);
    }
}

这对我不起作用。对于数据中的@JsonProperty(DataNamingStrategy.DATA_字段),不将泛型类型设置为“查询”的原因可能是什么。爪哇


共 (2) 个答案

  1. # 1 楼答案

    What could be the reason for not setting the generic type to "Inquiry" for @JsonProperty(DataNamingStrategy.DATA_FIELD) in Data.java

    原因是默认情况下JsonProperty注释的属性名不能被DataNamingStrategy重命名。Jackson有默认禁用的this功能

    ALLOW_EXPLICIT_PROPERTY_RENAMING Feature that when enabled will allow explicitly named properties (i.e., fields or methods annotated with JsonProperty("explicitName")) to be re-named by a PropertyNamingStrategy, if one is configured. Feature is disabled by default.

    Since: 2.7

    您只需启用此功能-

    ObjectMapper mapper = new ObjectMapper();
    mapper.enable(MapperFeature.ALLOW_EXPLICIT_PROPERTY_RENAMING);
    

    以下是相关的Jackson notes历史记录(参考here

    Prior versions allowed explicit property renaming by default
    v2.4 - Jackson stopped allowing property renaming. (#428)
    v2.7 - Introduced ALLOW_EXPLICIT_PROPERTY_RENAMING feature to allow / disallow Explicit Property renaming (#918)
    
       
    
  2. # 2 楼答案

    您可以直接使用WebClient和ParameterizedTypeReference类进行反序列化。以下是您的工作示例:

    以下代码的响应(查询和dummeyentity):

    // Inquiry Entity
    ThirdPartyAPIResponse(meta=ThirdPartyAPIResponse.Meta(rid=abc9f-defgh-hj78k-lkm9n, qid=abc9f-defgh-hj78k-lkm9n), data=ThirdPartyAPIResponse.Data(data=Inquiry(multiCurrency=[MultiCurrencyInq(taxStat=Y, taxAmt=1)], type=Tax, taxFreq=TaxFreq(weekDay=1, startDay=1), taxRegion=Tx)))
    
    // Dummy Entity
    ThirdPartyAPIResponse(meta=ThirdPartyAPIResponse.Meta(rid=abc9f-defgh-hj78k-lkm9n, qid=abc9f-defgh-hj78k-lkm9n), data=ThirdPartyAPIResponse.Data(data=DummyEntity(code=200, message=Hello World)))
    

    查询

    package com.stackoverflow.q69665171.entities;
    
    import java.util.List;
    
    import com.fasterxml.jackson.annotation.JsonProperty;
    
    import lombok.Getter;
    import lombok.Setter;
    import lombok.ToString;
    
    @Getter @Setter @ToString
    public class Inquiry {
        
        @JsonProperty("multiCurrency") private List<MultiCurrencyInq> multiCurrency;
        @JsonProperty("Type") private String type;
        @JsonProperty("TaxFreq") private TaxFreq taxFreq;
        @JsonProperty("TaxRegion") private String taxRegion;
        
    }
    

    多币种

    package com.stackoverflow.q69665171.entities;
    
    import com.fasterxml.jackson.annotation.JsonProperty;
    
    import lombok.Getter;
    import lombok.Setter;
    import lombok.ToString;
    
    @Getter @Setter @ToString
    public class MultiCurrencyInq {
    
        @JsonProperty("TaxStat") private String taxStat;
        @JsonProperty("TaxAmt") private Integer taxAmt;
        
    }
    

    TaxFreq

    package com.stackoverflow.q69665171.entities;
    
    import com.fasterxml.jackson.annotation.JsonProperty;
    
    import lombok.Getter;
    import lombok.Setter;
    import lombok.ToString;
    
    @Getter @Setter @ToString
    public class TaxFreq {
    
        @JsonProperty("weekDay") private Integer weekDay;
        @JsonProperty("startDay") private Integer startDay;
        
    }
    

    虚拟实体

    package com.stackoverflow.q69665171.entities;
    
    import com.fasterxml.jackson.annotation.JsonProperty;
    
    import lombok.Getter;
    import lombok.Setter;
    import lombok.ToString;
    
    @Getter @Setter @ToString
    public class DummyEntity {
    
        @JsonProperty("code") private Integer code;
        @JsonProperty("message") private String message;
        
    }
    

    第三方客户端(WebClient Impl)

    package com.stackoverflow.q69665171.third_party;
    
    import org.springframework.core.ParameterizedTypeReference;
    import org.springframework.stereotype.Component;
    import org.springframework.web.reactive.function.client.WebClient;
    
    import com.stackoverflow.q69665171.entities.Inquiry;
    
    @Component
    public class ThirdPartyAPIClient {
    
        private WebClient webClient;
    
        public ThirdPartyAPIClient() {
            webClient = WebClient.create();
        }
        
        // The port is for testing scopes only, as the mock web server
        // defines a random one
        public ThirdPartyAPIResponse<Inquiry> getInquiryApiResponse(int port) {
            return webClient.get()
                .uri("http://localhost:" + port + "/test")
                .retrieve()
                .bodyToMono(
                    new ParameterizedTypeReference<ThirdPartyAPIResponse<Inquiry>>(){}
                ).block();
        }
    
        public ThirdPartyAPIResponse<DummyEntity> getDummyEntityApiResponse(int port) {
            return webClient.get()
                .uri("http://localhost:" + port + "/test2")
                .retrieve()
                .bodyToMono(
                    new ParameterizedTypeReference<ThirdPartyAPIResponse<DummyEntity>>(){}
                ).block();
        }
    
    }
    

    第三方响应

    package com.stackoverflow.q69665171.third_party;
    
    import com.fasterxml.jackson.annotation.JsonAlias;
    import com.fasterxml.jackson.annotation.JsonProperty;
    
    import lombok.Getter;
    import lombok.Setter;
    import lombok.ToString;
    
    @Getter @Setter @ToString
    public class ThirdPartyAPIResponse <T> {
    
        @JsonProperty("meta") private Meta meta;
        @JsonProperty("data") private Data <T> data;
        
        @Getter @Setter @ToString
        public static class Meta {
            @JsonProperty("RID") private String rid;
            @JsonProperty("QID") private String qid;
        }
        
        @Getter @Setter @ToString
        public static class Data <T> {
            @JsonAlias({"Inquiry","DummyEntity"}) private T data;
        }
        
    }
    

    Json虚拟实体

    {
        "meta":{
           "RID":"abc9f-defgh-hj78k-lkm9n",
           "QID":"abc9f-defgh-hj78k-lkm9n"
        },
        "data":{
           "DummyEntity":{
              "code": 200,
              "message": "Hello World"
           }
        }
     }
    

    测试

    package com.stackoverflow.q69665171;
    
    import static org.junit.Assert.assertEquals;
    
    import java.io.File;
    import java.io.IOException;
    import java.nio.file.Files;
    
    import org.junit.jupiter.api.AfterAll;
    import org.junit.jupiter.api.BeforeAll;
    import org.junit.jupiter.api.Test;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.test.context.SpringBootTest;
    import org.springframework.core.io.ClassPathResource;
    import org.springframework.http.HttpHeaders;
    import org.springframework.http.MediaType;
    
    import com.stackoverflow.q69665171.third_party.ThirdPartyAPIClient;
    
    import okhttp3.mockwebserver.MockResponse;
    import okhttp3.mockwebserver.MockWebServer;
    
    @SpringBootTest
    class ApplicationTests {
    
        private static MockWebServer webServer;
        @Autowired private ThirdPartyAPIClient client;
        
        @BeforeAll
        static void setUp() throws IOException {
            webServer = new MockWebServer();
            webServer.start();
        }
        
        @AfterAll
        static void tearDown() throws IOException {
            webServer.shutdown();
        }
    
        
        @Test
        void should_Retrieve_Inquiry_Response_When_Consumes_Inquiry_API() throws Exception {
            
            final String thirdPartyResponse = readJsonTestResource("response.json");
            //System.out.println(thirdPartyResponse);
            
            // Mock
            webServer.enqueue(new MockResponse().setResponseCode(200)
                .setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
                .setBody(thirdPartyResponse)
            );
            
            System.out.println(client.getInquiryApiResponse(webServer.getPort()));
            
            assertEquals(0, 0);
            
        }
    
        private String readJsonTestResource(String fileName) throws Exception {
            File resource = new ClassPathResource(fileName).getFile();
            return new String(Files.readAllBytes(resource.toPath()));
        }
    
        
    
        @Test
        void should_Retrieve_Dummy_Entity_Response_When_Consumes_Dummy_API() throws Exception {
            
            final String thirdPartyResponse = readJsonTestResource("response2.json");
            //System.out.println(thirdPartyResponse);
            
            // Mock
            webServer.enqueue(new MockResponse().setResponseCode(200)
                .setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
                .setBody(thirdPartyResponse)
            );
            
            System.out.println(client.getDummyEntityApiResponse(webServer.getPort()));
            
            assertEquals(0, 0);
            
        }
    
    }
    

    项目结构

    enter image description here

    Pom

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
        <parent>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-parent</artifactId>
            <version>2.5.6</version>
            <relativePath /> <!  lookup parent from repository  >
        </parent>
        <groupId>com.stackoverflow.q69665171</groupId>
        <artifactId>69665171</artifactId>
        <version>0.0.1-SNAPSHOT</version>
        <name>69665171</name>
        <description>Demo project for Spring Boot</description>
        <properties>
            <java.version>11</java.version>
        </properties>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-webflux</artifactId>
            </dependency>
    
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <optional>true</optional>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-test</artifactId>
                <scope>test</scope>
            </dependency>
            <dependency>
                <groupId>io.projectreactor</groupId>
                <artifactId>reactor-test</artifactId>
                <scope>test</scope>
            </dependency>
            <dependency>
                <groupId>com.squareup.okhttp3</groupId>
                <artifactId>okhttp</artifactId>
                <scope>test</scope>
            </dependency>
            <dependency>
                <groupId>com.squareup.okhttp3</groupId>
                <artifactId>mockwebserver</artifactId>
                <scope>test</scope>
            </dependency>
        </dependencies>
    
        <build>
            <plugins>
                <plugin>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-maven-plugin</artifactId>
                    <configuration>
                        <excludes>
                            <exclude>
                                <groupId>org.projectlombok</groupId>
                                <artifactId>lombok</artifactId>
                            </exclude>
                        </excludes>
                    </configuration>
                </plugin>
            </plugins>
        </build>
    
    </project>
    

    上面的相同示例适用于Jackson,但使用TypeReference类代替ParameterizedTypeReference,但为了优化,最好使用相同的WebClient执行反序列化