一种在json场景针对pojo对象动态添加扁平属性的方法

本方法在几年前均已实现,这里将其重新整理一下,以作备忘
在典型的应用场景中,经常会有这样的需求,即当业务返回单个pojo对象时,需要临时追加几个属性在这个对象中并一起返回至前端。如下例子所示:

public class Abc {
    String username;
}

此对象仅有1个属性,但返回至前端时,需要返回类似如下的数据结构:

{
    "username": "张三",
    "sex": "MALE",
    "age": 20
}

有一些作法通过再定义新的类,如 AbcVO 通过继承原类并添加新字段来支持;或者不再使用对象,则是直接使用map代替. 前者会造成类爆炸,后者会造成API语义不清晰.

本文描述了一种标准的 Attr 接口结构,并通过json序列化器(如jackson或fastjson)支持的注解,通过简单的default 方法定义,完成属性添加。并同时支持序列化和反序列化的应用场景.

这里采用接口,以及default 方法设计,原有的类通过一个简单的额外implements,即可支持此特性,无需其它的改造,也不需要额外实现接口方法,即可使用此特性.

对应于典型的setter和getter设计,此接口需要有set,get以及与map语义相类似的定义,以支持json化处理,典型的定义如下所示(类及方法名仅供参考):

public interface Attr {
    default Map<String, Object> all() {
        //实现
    }

    default void set(String k, Object v) {
        //实现
    }

    default Object get(String k) {
        //实现
    }
}

针对之前的定义,那么相应的业务代码应如下使用

val v = new Abc();
v.setUsername(); //正常字段处理
v.set("sex", "MALE"); //扩展字段处理

val s = toJson(v); //序列化
//相应数据为 {username:"xxx", sex:"MALE"}

val v2 = parseJson(s); //反序列化
String extV1 = v2.get("sex"); 
//这里反序列化同样能拿到 序列化之前的 MALE 值

上面接口的定义中,还需要考虑各类json框架的支持,其中 jackson 中可以使用 @JsonAnySetter 和 @JsonAnyGetter 来处理。而在fastjson 中,可以使用 JSONField(unwrapped=true) 来支持。因此,完整方法定义如下

public interface Attr {
    @JsonAnyGetter
    @JSONField(name = "_any", unwrapped = true, serialize = true, deserialize = false)
    default Map<String, Object> all() {
        //实现
    }

    @JsonAnySetter
    @JSONField(name = "_any", unwrapped = true, serialize = false, deserialize = false)
    default void set(String k, Object v) {
        //实现
    }
}

其中fastjson的注解定义可参考此文 https://github.com/alibaba/fastjson/wiki/JSONField_unwrapped_cn

接下来是 default 方法的实现,由于接口方法内不允许有字段,因此相应的实现均转换为外部静态方法调用,可以以 this 为key, 额外的map 为 value 来模拟相应的实现. 比如 set 的实现如下所示

default void set(String k, Object v) {
    val map = Utils.computeIfAbsent(this, "_v", Maps::newHashMap);
    map.put(name, value);
}

public class Utils {
    public static final Cache<Object, Map<String, Object>> cache; //全局 static 大cache map

    public static <A, H> A computeIfAbsent(H holder, String attrName, Supplier<A> absentSupplier) {
        Map<String, Object> map = cache.get(holder, t -> Maps.newHashMap());

        return (A) map.computeIfAbsent(attrName, noused -> absentSupplier.get());
    }
}

以上代码仅供参考,有一定的优化空间.

后记

经过以上的处理,通过 全局cache, weak引用, json注解处理,可以完成一个基本的可临时扩展属性类的设计。在实际的使用过程中,还需要考虑 cache 中对象创建和回收压力。在某些场景下,还可能出现循环引用导致 cache 中数据不能回收的情况. 当然,也有一些通过 extends 默认实现类来规避 cache的实现. 这里描述相应的思路.

转载请标明出处:i flym
本文地址:https://www.iflym.com/index.php/code/202103220001.html

相关文章:

作者: flym

I am flym,the master of the site:)

发表评论

邮箱地址不会被公开。 必填项已用*标注