你也遇到JSONException:create instance error, null...问题啦?

时间:2025-04-03 18:19:08

近期在工作中踩到的坑,返回结果使用Result<T>封装,结果踩了两个FastJson与构造方法的大坑,分享下,注意别踩到相同的坑。

1. 测试代码

  1. 创建了Result<String>对象,序列化为JSON字符串
  2. 将JSON字符串反序列化为Result<String>对象
    public static void main(String[] args) {
            // 创建对象
            Result<String> result = new Result<String>()
                    .setRet(-1)
                    .setData(null)
                    .setError(new (404).addErrorDetail("id", 9, ""));
            // 将对象序列化为JSON字符串
            String resultJson = (result,
                    ,
                    );
            (resultJson);
    ​
            // 反序列化
            Result<String> resultFromJson = (resultJson, new TypeReference<Result<String>>() {
            });
            (resultFromJson);
     }

    序列化后的JSON字符串

    {
    	"data":null,
    	"error":{
    		"code":404,
    		"details":[
    			{
    				"data":9,
    				"field":"id",
    				"msg":""
    			}
    		]
    	},
    	"ret":-1
    }

    和构造方法有关。creatorConstructor被确定为Error(int code)这个构造方法,而这个构造方法就只有int code 这个一个参数,在反序列化创建时,就这完成了code字段的赋值,而没有反序列化details字段。

解决方案

  • 增加构造方法Error(int code, List<ErrorDetail> details)
  • Result<T>增加无参构造方法
    至此,问题解决。

小结

  • JSON反序列化与构造方法密切相关,推荐每个类都提供无参构造方法
  • 如果某个类从设计上不适合提供无参构造方法,需要特别注意以上两个问题。推荐提供全参构造方法。

相关类定义

正确的Result<T>

import ;
import ;

import ;
import ;


@Data
@Accessors(chain = true)
public class Result<T> {

    /**
     * ret >= 0 success;
     * ret < 0  error;
     */
    private int ret;

    private T data;

    private Error error;

    @Data
    @Accessors(chain = true)
    public static class Error {

        private int code;

        private List<ErrorDetail> details;

        public Error(int code) {
             = code;
             = new ArrayList<>();
        }

        /**
         * 特别注意,改构造方法千万不要放到上一个构造方法前,否则会导致FastJson反序列出问题。
         * <p>
         * public Error(CmnCode code) {
         *  = ();
         *  = new ArrayList<>();
         * }
         */

        public Error(int code, List<ErrorDetail> details) {
             = code;
            if (null == details) {
                 = new ArrayList<>();
            } else {
                 = details;
            }
        }

        public Error addErrorDetail(String msg) {
            (new ()
                    .setMsg(msg));
            return this;
        }

        public Error addErrorDetail(String field, String msg) {
            (new ()
                    .setField(field)
                    .setMsg(msg));
            return this;
        }

        public Error addErrorDetail(Object data, String msg) {
            (new ()
                    .setData(data)
                    .setMsg(msg));
            return this;
        }

        public Error addErrorDetail(String field, Object data, String msg) {
            (new ()
                    .setField(field)
                    .setData(data)
                    .setMsg(msg));
            return this;
        }
    }

    @Data
    @Accessors(chain = true)
    public static class ErrorDetail {

        /**
         * error field;
         */
        private String field;

        /**
         * error field value;
         */
        private Object data;

        /**
         * error field message;
         */
        private String msg;
    }
}

CmnCode枚举

import ;


public enum CmnCode implements Serializable {

    /**
     * ok
     */
    OK(200),

    BAD_REQUEST(400),
    UNAUTHORIZED(401),
    FORBIDDEN(403),
    NOT_FOUND(404),
    METHOD_NOT_ALLOWED(405),
    NOT_ACCEPTABLE(406),
    CONFLICT(409),
    PRECONDITION_FAILED(412),
    UNSUPPORTED_MEDIA_TYPE(415),
    PROTOCOL_NOT_MATCH(444),
    INTERNAL_ERROR(500),
    GATEWAY_ERROR(502),
    SERVICE_UNAVAILABLE(503),
    GATEWAY_TIMEOUT(504);

    private int code;

    CmnCode(int code) {
         = code;
    }

    public static CmnCode fromHttpStatus(int httpStatus) {
        for (CmnCode cmnCode : values()) {
            if (() == httpStatus) {
                return cmnCode;
            }
        }
        return INTERNAL_ERROR;
    }

    public int getCode() {
        return code;
    }

    public CmnCode setCode(int code) {
         = code;
        return this;
    }
}

引发问题的Result<T>

@Data
@Accessors(chain = true)
public class Result<T> {

    /**
     * ret >= 0 success;
     * ret < 0  error;
     */
    private int ret;
    
    private T data;

    private Error error;

    @Data
    @Accessors(chain = true)
    public static class Error {

        private int code;

        private List<ErrorDetail> details;

        public Error(CmnCode cmnCode) {
             = ();
             = new ArrayList<>();
        }

        public Error(int code) {
             = code;
             = new ArrayList<>();
        }
    }
}