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

司空福
2023-12-01

近期在工作中踩到的坑,返回结果使用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 Result.Error(404).addErrorDetail("id", 9, "resource.not.found"));
            // 将对象序列化为JSON字符串
            String resultJson = JSONObject.toJSONString(result,
                    SerializerFeature.WriteMapNullValue,
                    SerializerFeature.PrettyFormat);
            System.out.println(resultJson);
    ​
            // 反序列化
            Result<String> resultFromJson = JSON.parseObject(resultJson, new TypeReference<Result<String>>() {
            });
            System.out.println(resultFromJson);
     }

    序列化后的JSON字符串

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

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

解决方案

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

小结

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

相关类定义

正确的Result<T>

import lombok.Data;
import lombok.experimental.Accessors;

import java.util.ArrayList;
import java.util.List;


@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) {
            this.code = code;
            this.details = new ArrayList<>();
        }

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

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

        public Error addErrorDetail(String msg) {
            this.details.add(new Result.ErrorDetail()
                    .setMsg(msg));
            return this;
        }

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

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

        public Error addErrorDetail(String field, Object data, String msg) {
            this.details.add(new Result.ErrorDetail()
                    .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 java.io.Serializable;


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) {
        this.code = code;
    }

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

    public int getCode() {
        return code;
    }

    public CmnCode setCode(int code) {
        this.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) {
            this.code = cmnCode.getCode();
            this.details = new ArrayList<>();
        }

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

 类似资料: