使用 fastjson 解决字段串对象空值返回空数组问题

Posted carl-zhao

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了使用 fastjson 解决字段串对象空值返回空数组问题相关的知识,希望对你有一定的参考价值。

我们系统当中地理信息使用高德的 城市编码表。项目的新需求就是输入地址能够返回找到对应的城市,其实高德开放 API 提供了一个 地理/逆地理编码 接口是满足这个需求的。但是它我们使用这个接口的时候遇到了一个问题。就是一个属性如果有值就会返回字符串,如果没有值就会返回空数据。比如:

{
    "status": "1",
    "info": "OK",
    "infocode": "10000",
    "count": "1",
    "geocodes": [
        {
            "formatted_address": "河南省洛阳市",
            "country": "中国",
            "province": "河南省",
            "citycode": "0379",
            "city": "洛阳市",
            "district": [],
            "township": [],
            "neighborhood": {
                "name": [],
                "type": []
            },
            "building": {
                "name": [],
                "type": []
            },
            "adcode": "410300",
            "street": [],
            "number": [],
            "location": "112.454040,34.619682",
            "level": "市"
        }
    ]
}

上面地区(district) 这个字段,在有值的时候就会返回字符串,在为空值的时候就会返回空数组。在项目当中我们使用 RestTemplate 来进行第三方接口调用,并且我们定义对象的时候使用 String 来接收这个值。这样在反序列化的时候就会报错。

最开始的时候我想到的是通过 Jackson 自定义反序列化来解决这个问题。

StringDeserialize.java

public class StringDeserialize extends JsonDeserializer<String> {
    @SneakyThrows
    @Override
    public String deserialize(JsonParser p, DeserializationContext ctxt) {
        if ("[".equals(p.getText())) {
            return null;
        }
        return p.getText();
    }
}

下面就是自己定义的 jackson 反序化的类把它通过以下方式标注到需要自定义的反序列化的字段上就可以了。

@JsonSerialize(using = StringDeserialize.class)
private String district;

本来我在测试的时候使用自己的对象并没有使用与高德地图属性对应的对象(方便测试),自己定义的 pojo 只有单个属性的时候没有问题,但是在测试高德地图返回的数据时就遇到了以下的问题。

本来返回的 geocodes 就是一个对象。但是因为我自定义反序列变成了两个对象。然后我尝试看了一下使用 jaskson 其它方式来解决问题,都没有达到效果。我隐约记得 fastjson 可以反序列化成功这个问题,后面我就使用了 fastson。

反序列化没有报错,district 属性空数组被反序列化成了 "[]" 字符串。虽然解决了我们反序列化问题,但是理想情况下应该是返回空值才比较好。跟踪源码发现 String 反序列方式是通过 StringCodec 类来进行的。

StringCodec#deserialze(com.alibaba.fastjson.parser.DefaultJSONParser)


上面的逻辑挺清晰的:

  • 如果是字符串,获取字符串值返回
  • 如果是 Int 类型的值,进行数值转字符串值返回
  • 如果是其它类型的值调用对象的 toString 方法获取字符串返回

其实 fastjson 是支持我们自定义参数序列化的,就是通过 @JSONFielddeserializeUsing 方法指定自定义的序列化方式。那么我们定义序列化逻辑就比较简单,就是在调用对象调用对象的 toString 方法获取字符串返回之前加上判断它是否是 JSONArray,如果是 JSONArray 并且是空的话就直接返回 null

CustomStringCodec.java

public class CustomStringCodec extends StringCodec {

    @Override
    public <T> T deserialze(DefaultJSONParser parser, Type clazz, Object fieldName) {
        if (clazz == StringBuffer.class) {
            final JSONLexer lexer = parser.lexer;
            if (lexer.token() == JSONToken.LITERAL_STRING) {
                String val = lexer.stringVal();
                lexer.nextToken(JSONToken.COMMA);

                return (T) new StringBuffer(val);
            }

            Object value = parser.parse();

            if (value == null) {
                return null;
            }

            return (T) new StringBuffer(value.toString());
        }

        if (clazz == StringBuilder.class) {
            final JSONLexer lexer = parser.lexer;
            if (lexer.token() == JSONToken.LITERAL_STRING) {
                String val = lexer.stringVal();
                lexer.nextToken(JSONToken.COMMA);

                return (T) new StringBuilder(val);
            }

            Object value = parser.parse();

            if (value == null) {
                return null;
            }

            return (T) new StringBuilder(value.toString());
        }
        return (T) deserialze(parser);
    }

    public static <T> T deserialze(DefaultJSONParser parser) {
        final JSONLexer lexer = parser.getLexer();
        if (lexer.token() == JSONToken.LITERAL_STRING) {
            String val = lexer.stringVal();
            lexer.nextToken(JSONToken.COMMA);
            return (T) val;
        }

        if (lexer.token() == JSONToken.LITERAL_INT) {
            String val = lexer.numberString();
            lexer.nextToken(JSONToken.COMMA);
            return (T) val;
        }

        Object value = parser.parse();

        if (value instanceof JSONArray) {
            JSONArray ja = JSONArray.class.cast(value);
            if(ja.size() == 0) {
                return null;
            }
        }

        if (value == null) {
            return null;
        }

        return (T) value.toString();
    }

}

然后我们在地区属性添加 @JSONField,如下:

@JSONField(deserializeUsing = CustomStringCodec.class)
private String district;

然后运行我们的测试用例:


调用第三方高德的 地理/逆地理编码 接口时,我们就只需要通过 String 对象接收响应对象。然后再通过 fastjson 来序列成 pojo 响应对象就行了。

以上是关于使用 fastjson 解决字段串对象空值返回空数组问题的主要内容,如果未能解决你的问题,请参考以下文章

Fastjson vs Jackson, Jackson配置Null时返回空值

关于fastjson在序列化成JSON串时字段增加的问题

fastjson序列化将null变成空字符串

fastjson过滤掉不需要返回的字段

为什么fastjson字段为null时不输出空字符串?

关于FastJSON对象转字符串,对象有字段为null会自动忽略