TongchengOpenSource/smart-doc

Request parameters for wrapper classes are not currently supported, although I added generics

Closed this issue · 11 comments

Contact Details

jiangmang.1992@163.com

Version

3.0.7

Plugin

smart-doc-maven-plugin

Build tool version

maven 3.6.2

Jdk version

17

Describe the bug (Bug描述,社区已开启国际化推广,请用文心一言、讯飞星火等辅助翻译成英文,减少社区开发者的工作)

My request object is CommonRequest, which has two properties, header and body, Where the body is a generic wrapper object. I used generics on the requested api interface, specifying the type of body. But there is no structure on the document to generate the body.

Expected Behavior (您期望的结果,社区已开启国际化推广,请用文心一言、讯飞星火等辅助翻译成英文,减少社区开发者的工作)

Expected to support wrapper classes as request parameters and return results

Current Behavior (当前结果,社区已开启国际化推广,请用文心一言、讯飞星火等辅助翻译成英文,减少社区开发者的工作)

Structure has only header, no body

Steps to Reproduce (Bug产生步骤,请尽量提供用例代码。社区已开启国际化推广,请用文心一言、讯飞星火等辅助翻译成英文,减少社区开发者的工作)

With this wrapper class,

you can reproduce the problem.

Possible Solution (Bug解决建议,社区已开启国际化推广,请用文心一言、讯飞星火等辅助翻译成英文,减少社区开发者的工作)

No response

Context (Bug影响描述,社区已开启国际化推广,请用文心一言、讯飞星火等辅助翻译成英文,减少社区开发者的工作)

No response

Validations

  • Check if you're on the latest smart-doc version. (请检查是否为最新版本)
  • Read the docs. (请先阅读官方文档)
  • Check that there isn't already an issue that reports the same bug to avoid creating a duplicate. (检查是否存在报告相同错误的问题,以避免产生重复)

@jay763190097 Can you provide a test case? The code sample should include a Controller interface and CommonRequest. You can include the test code in the issue.

@shalousun 就是文档里面的参数对象,其实现在用的很多,为了统一结构。
CommonRequest来自一个lib包

public class CommonRequest<T> {

    private RequesterHeader header;
    private transient T body;

}
/**
 * customer creation api
 * @param request com.abc.CustomerCreateDto
 * @return
 *
 */
@PostMapping("/customer/create")
public CommonResult<CustomerCreateResDto> create(@RequestBody @Valid CommonRequest<CustomerCreateDto> request) {
} 

文档值生成了CommonRequest里面的header结构,body完全没有。。。加不加param都是这样的。
header能正常看到结构,证明不是外部源代码的问题。

@jay763190097 Fields modified by the transient keyword are ignored by smart-doc.

@shalousun The transient keyword, which is used for deserialization in the request body, makes sense, Because it will no longer be serialized. The idea is that even if it is serialized, it reduces the overhead of serialization, so it should be parsed. If it is in the response body, we should ignore it because this is a serialization operation.

@jay763190097 Let's first test different JSON serialization scenarios, and then make a fix based on the actual results.

@shalousun for serialization scenario, we use transient to ignore this field, that‘s right.
But for de-serialization scenario, we should parse this filed.

@shalousun for serialization scenario, we use transient to ignore this field, that‘s right. But for de-serialization scenario, we should parse this filed.

Through testing, it has been found that the handling of fields modified with transient varies across different JSON processing frameworks. In Jackson, such fields are not ignored by default, whereas in Fastjson, they are ignored. As a result, the community has had to provide a separate configuration option to determine whether fields marked with transient should be included or not.


Additional Explanation: Handling of transient Fields by Different JSON Serialization Frameworks

Different JSON serialization frameworks handle transient fields in various ways. The following code demonstrates these differences:

Example Class Demo

package com.power.doc.model;

import lombok.Data;

import java.io.Serial;
import java.io.Serializable;

@Data
public class TestTransient implements Serializable {

    @Serial
    private static final long serialVersionUID = 1L;

    /**
     * name
     */
    private String name;
    /**
     * password
     */
    private transient String password;

    @Override
    public String toString() {
        return "TestTransient{" +
                "name='" + name + '\'' +
                ", password='" + password + '\'' +
                '}';
    }
}

Example Junit Test Class

package com.power.doc.utils;

import com.alibaba.fastjson2.JSON;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.gson.Gson;
import com.power.doc.model.TestTransient;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

/**
 * json transient test
 */
public class JsonTransientTest {


    /**
     * Test Json transient filed
     */
    @DisplayName("Test Json transient filed")
    @Test
    public void test() throws JsonProcessingException {
        TestTransient testTransient = new TestTransient();
        testTransient.setName("test");
        testTransient.setPassword("123456");

        ObjectMapper objectMapper = new ObjectMapper();


        System.out.println("com.alibaba.fastjson.JSON:" + com.alibaba.fastjson.JSON.toJSON(testTransient));
        System.out.println("com.alibaba.fastjson2.JSON:" + JSON.toJSON(testTransient));
        System.out.println("com.fasterxml.jackson.databind.ObjectMapper:" + objectMapper.writeValueAsString(testTransient));
        System.out.println("com.google.gson.Gson:" + new Gson().toJson(testTransient));

//        com.alibaba.fastjson.JSON:{"password":"123456","name":"test"}
//        com.alibaba.fastjson2.JSON:{"name":"test"}
//        com.fasterxml.jackson.databind.ObjectMapper:{"name":"test","password":"123456"}
//        com.google.gson.Gson:{"name":"test"}

        System.out.println();

        String test = "{\"password\":\"123456\",\"name\":\"test\"}";
        System.out.println("com.alibaba.fastjson.JSON:" + com.alibaba.fastjson.JSON.parseObject(test, TestTransient.class));
        System.out.println("com.alibaba.fastjson2.JSON:" + JSON.parseObject(test, TestTransient.class));
        System.out.println("com.fasterxml.jackson.databind.ObjectMapper:" + objectMapper.readValue(test, TestTransient.class));
        System.out.println("com.google.gson.Gson:" + new Gson().fromJson(test, TestTransient.class));


//        com.alibaba.fastjson.JSON:TestTransient{name='test', password='123456'}
//        com.alibaba.fastjson2.JSON:TestTransient{name='test', password='123456'}
//        com.fasterxml.jackson.databind.ObjectMapper:TestTransient{name='test', password='123456'}
//        com.google.gson.Gson:TestTransient{name='test', password='null'}

    }
}

Analysis of Results:

  • Serialization:

    • com.alibaba.fastjson.JSON and com.fasterxml.jackson.databind.ObjectMapper include the transient field during serialization, producing {"name":"test","password":"123456"}.
    • com.alibaba.fastjson2.JSON and com.google.gson.Gson exclude the transient field during serialization, resulting in {"name":"test"}.
  • Deserialization:

    • com.alibaba.fastjson.JSON, com.alibaba.fastjson2.JSON, and com.fasterxml.jackson.databind.ObjectMapper successfully deserialize the transient field, producing TestTransient{name='test', password='123456'}.
    • com.google.gson.Gson does not deserialize the transient field, resulting in TestTransient{name='test', password='null'}.

Conclusion:

The handling of transient fields varies among different JSON frameworks. Below is a summary table that illustrates these differences:

Framework Serialization (Includes transient field) Deserialization (Restores transient field)
com.alibaba.fastjson.JSON Yes Yes
com.alibaba.fastjson2.JSON No Yes
com.fasterxml.jackson.databind.ObjectMapper Yes Yes
com.google.gson.Gson No No
  • Serialization: Whether the framework includes the transient field during JSON serialization.
  • Deserialization: Whether the framework restores the value of the transient field during JSON deserialization.

This table highlights that Fastjson (original version) and Jackson include transient fields in both serialization and deserialization. Fastjson2 excludes the transient field during serialization but correctly restores it during deserialization. Gson, on the other hand, excludes the transient field in both serialization and deserialization processes.


@jay763190097 For versions after 3.0.8, you can configure the serialization of fields marked with transients: reference configuration options:

{
  "serializeRequestTransients": true,
  "serializeResponseTransients": false
}

@shalousun @linwumingshi After checking the source code, I found a config property skipTransientField, the default value is true. I added this property and changed it to false, then It worked for me, didn't sync with you guys, my bad. Is it a duplicate property you are defined?

image
@shalousun @linwumingshi Thanks all, I checked the PR, you already considered the old config property, Cool !!!, I'll use the property serializeRequestTransients.