使用JSLT进行对象转换

一.介绍与使用

JSLT是一种完整的JSON查询和转换语言。git地址:https://github.com/schibsted/jslt

 

java中使用:

<dependency>
<groupId>com.schibsted.spt.data</groupId>
<artifactId>jslt</artifactId>
<version>0.1.11</version>
<scope>compile</scope>
</dependency>

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.schibsted.spt.data.jslt.Expression;
import com.schibsted.spt.data.jslt.Parser;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import java.io.IOException;

@Slf4j
public class JsltUtil {
    /**
     * 转换json字符串(忽略异常,当异常时返回null)
     * @param jsltStr   jslt表达式
     * @param inputStr  json字符串入参
     * @return
     */
    public static String jsonAdapt(String jsltStr, String inputStr) {
        try {
            return jsonAdapt(jsltStr, inputStr, true);
        } catch (Exception e) {
            e.printStackTrace();
            log.error("jsonAdapt error jsltStr={},inputStr={}", jsltStr, inputStr, e);
        }
        return null;
    }

    /**
     * 转换json字符串
     * @param jsltStr   jslt表达式
     * @param inputStr  json字符串入参
     * @param ignoreException  是否忽略异常 如果为ture,出现异常后返回null
     * @return
     */
    public static String jsonAdapt(String jsltStr, String inputStr, boolean ignoreException) throws IOException {
        if (StringUtils.isBlank(jsltStr) || StringUtils.isBlank(inputStr)) {
            log.info("jsonAdapt param illegal");
            return null;
        }
        try {
            ObjectMapper mapper = new ObjectMapper();
            JsonNode input = mapper.readTree(inputStr);
            Expression jslt = Parser.compileString(jsltStr);
            JsonNode output = jslt.apply(input);
            return output.toString();
        } catch (Exception e) {
            log.error("jsonAdapt error jsltStr={},inputStr={}", jsltStr, inputStr, e);
            if (!ignoreException) {
                throw e;
            }
        }
        return null;
    }
}

  

 

二.常用转换场景示例

 

1.普通对象转换

// jslt表达式
// crowd 根据age进行if判断输出人群
{
    "name":.name,
    "age":.age,
    "crowd":if(.age<18"未成年" else if(.age<60"成年人" else "老年人"
}
// 入参:
{
    "name":"nameTest",
    "age":10
}
// 结果:
{
  "name" "nameTest",
  "age" 10,
  "crowd" "未成年"
}

 

2.子对象转换

// jslt表达式
// * : . 匹配输入对象中的所有键,除了那些已经指定的,并将它们复制到输出对象中;如果有部分字段不想全拷贝使用 :* - bar, baz, quux : .
// 注意一点, * :  这个语法必须在最后,否则会报错    子对象中,没效果 "extendMsg3":{*:.}   这个字段直接不输出(只有当对象名和入参的一样时,才能在嵌套中再用*  ,eg:"extendMsg":{*:.} 是生效的)
{
"extendMsg1":.extendMsg,
"extendMsg2":{"married2":.extendMsg.married},
*:.
}
// 入参:
{
    "name":"nameTest",
    "age":10,
    "extendMsg":{"sex":"男","married":false,"education":"小学生"}
}
// 结果:
{
  "extendMsg1" : {
    "sex" "男",
    "married" false,
    "education" "小学生"
  },
  "extendMsg2" : {
    "married2" false
  },
  "name" "nameTest",
  "age" 10,
  "extendMsg" : {
    "sex" "男",
    "married" false,
    "education" "小学生"
  }
}

 

3.子对象平铺展开/对象缩进 转换

// 展开
// jslt表达式
{
"sex":.extendMsg.sex,
"married":.extendMsg.married,
* - extendMsg:.
}
// 入参:
{
    "name":"nameTest",
    "age":10,
    "extendMsg":{"sex":"男","married":false,"education":"小学生"}
}
// 结果:
{
  "sex" "男",
  "married" false,
  "name" "nameTest",
  "age" 10
}
 
 
 
// 缩进
// jslt表达式
{
"name":.name,
"age":.age,
"extendMsg":{"sex":.sex,"married":.married,"education":.education}
}
// 入参:
{
    "name":"nameTest",
    "age":10,
    "sex":"男",
    "married":false,
    "education":"小学生"
}
// 结果:
{
  "name" "nameTest",
  "age" 10,
  "extendMsg" : {
    "sex" "男",
    "married" false,
    "education" "小学生"
  }
}

 

4.集合转换

/** 把集合全部转换 */
// jslt表达式
[for(.) {*:.}]
// 入参:
[
    {
        "name":"nameTest",
        "age":10,
        "sex":"男",
        "married":false,
        "education":"小学生"
    },
    {
        "name":"nameTest2",
        "age":102,
        "sex":"女",
        "married":true,
        "education":"女博士"
    }
]
// 结果:
// 输出和入参相同
 
 
/** 集合部分转换 */
// jslt表达式
[for(.) {"name":.name,"age":.age,"test":"testxxx"}]
// 结果:
[ {
  "name" "nameTest",
  "age" 10,
  "test" "testxxx"
}, {
  "name" "nameTest2",
  "age" 102,
  "test" "testxxx"
} ]

 

5.复杂对象(分页为例)转换

/** 1.复杂组合=基础+集合 转换;2.haveNextPaga 举例变量计算 */
// jslt表达式
{
"pageIndex":.pageNo,
"pageSize":.pageSize,
"total":.total,
"haveNextPaga":if((.total - .pageNo * .pageSize) > 0true else false,
"data":[for(.list) {*:.}]
}
// 入参:
{
    "pageNo":1,
    "pageSize":2,
    "total":100,
    "list":[
        {
            "name":"nameTest",
            "age":10,
            "sex":"男",
            "married":false,
            "education":"小学生"
        },
        {
            "name":"nameTest2",
            "age":102,
            "sex":"女",
            "married":true,
            "education":"女博士"
        }
    ]
}
// 结果:
{
  "pageIndex" : 1,
  "pageSize" : 2,
  "total" : 100,
  "haveNextPaga" : true,
  "data" : [ {
    "name" : "nameTest",
    "age" : 10,
    "sex" : "男",
    "married" : false,
    "education" : "小学生"
  }, {
    "name" : "nameTest2",
    "age" : 102,
    "sex" : "女",
    "married" : true,
    "education" : "女博士"
  } ]
}

 

6.取集合中的某一个对象

// jslt表达式
{"data1":.[0].name,"data2":.[0].age}
// 入参:
[
    {
        "name":"nameTest",
        "age":10,
        "sex":"男",
        "married":false,
        "education":"小学生"
    },
    {
        "name":"nameTest2",
        "age":102,
        "sex":"女",
        "married":true,
        "education":"女博士"
    }
]
// 结果:{
 
  "data1" "nameTest",
  "data2" 10
}

 

三.可视化界面生成表达式

可进行两个事情:1.实现入下所示通过列表自动生成jslt表达式;2.通过json生成列表


 

 

生成的jslt表达式: 

{"a1":.a, "b1":{"c1":.c, "d1":.d}, "e1":.e, "g1":[for(.g){"a2":.ga, "b2":.gb}]}

入参json:

{"a":"a","c":"c","d":"d","e":"e","g":[{"ga":"ga","gb":"gb"},{"ga":"ga2","gb":"gb2"},{"ga":"ga3","gb":"gb3"}]}

出参json:

{"a1":"a","b1":{"c1":"c","d1":"d"},"e1":"e","g1":[{"a2":"ga","b2":"gb"},{"a2":"ga2","b2":"gb2"},{"a2":"ga3","b2":"gb3"}]}

ObjectAdaptMappingInfoBO (配置对象)
public class ObjectAdaptMappingInfoBO implements Serializable {
    /**
     * id
     */
    private Long id;

    /**
     * 对象映射id
     */
    private Long adaptId;

    /**
     * 原始字段
     */
    private String sourceColumn;

    /**
     * 目标字段
     */
    private String targetColumn;

    /**
     * 对象类型:1:基本类型,2:object,3:集合
     * @see ObjectAdaptTypeEnum
     */
    private Byte targetType;

    /**
     * 创建时间
     */
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone="GMT+8")
    private Date createTime;

    /**
     * 创建人
     */
    private String creator;

    /**
     * 更新时间
     */
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone="GMT+8")
    private Date updateTime;

    /**
     * 修改人
     */
    private String updator;

    /**
     * 是否有效 0无效 1有效
     */
    private Byte yn;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public Long getAdaptId() {
        return adaptId;
    }

    public void setAdaptId(Long adaptId) {
        this.adaptId = adaptId;
    }

    public String getSourceColumn() {
        return sourceColumn;
    }

    public void setSourceColumn(String sourceColumn) {
        this.sourceColumn = sourceColumn;
    }

    public String getTargetColumn() {
        return targetColumn;
    }

    public void setTargetColumn(String targetColumn) {
        this.targetColumn = targetColumn;
    }

    public Byte getTargetType() {
        return targetType;
    }

    public void setTargetType(Byte targetType) {
        this.targetType = targetType;
    }

    public Date getCreateTime() {
        return createTime;
    }

    public void setCreateTime(Date createTime) {
        this.createTime = createTime;
    }

    public String getCreator() {
        return creator;
    }

    public void setCreator(String creator) {
        this.creator = creator;
    }

    public Date getUpdateTime() {
        return updateTime;
    }

    public void setUpdateTime(Date updateTime) {
        this.updateTime = updateTime;
    }

    public String getUpdator() {
        return updator;
    }

    public void setUpdator(String updator) {
        this.updator = updator;
    }

    public Byte getYn() {
        return yn;
    }

    public void setYn(Byte yn) {
        this.yn = yn;
    }
}
View Code
JsltConvertUtil (转换工具类)
@Slf4j
public class JsltConvertUtil {
    /**
     * 通过mapping配置,生成jslt表达式
     * @param list
     * @param type
     * @param parentName
     * @return
     */
    public static String convert(List<ObjectAdaptMappingInfoBO> list, Byte type, String parentName) {
        String result = "";

        String collectionName = "";
        List<ObjectAdaptMappingInfoBO> selfList = getSelfList(list, type, parentName);
        if (CollectionUtils.isEmpty(selfList)) {
            return returnConvertByType(result, type, collectionName);
        }
        Set<String> existColumn = new HashSet<>();
        for (ObjectAdaptMappingInfoBO mappingInfoBO : selfList) {
            try {
                if (mappingInfoBO == null || StringUtil.isBlank(mappingInfoBO.getTargetColumn())) {
                    continue;
                }
                String targetColumn = mappingInfoBO.getTargetColumn();
                String sourceColumn = mappingInfoBO.getSourceColumn();
                // 如果原字段没有对应值,不返回
                if (StringUtil.isBlank(sourceColumn)) {
                    continue;
                }
                if (ObjectAdaptTypeEnum.COLLECTION.getCode().equals(type)) {
                    String[] split = sourceColumn.split("\\.");
                    collectionName = split[0];
                    sourceColumn = split.length > 1 ? split[1] : split[0];
                }

                boolean haveChild = false;
                if (targetColumn.contains(".")) {
                    haveChild = true;
                    String[] split = targetColumn.split("\\.");
                    targetColumn = split[0];
                }
                // targetColumn之前已添加过,跳出
                if (!existColumn.add(targetColumn)) {
                    continue;
                }

                // 不是第一次,字符串就不为空,添加一个分割符
                if (result != "") {
                    result += ", ";
                }
                result += "\"" + targetColumn + "\":";
                if (!haveChild) {
                    result += "." + sourceColumn;
                } else {
                    // 还有子层级,递归调用
                    result += convert(selfList, mappingInfoBO.getTargetType(), targetColumn);
                }
            } catch (Exception e) {
                log.error("JsltConvertUtil convert error list={}, type, parentName", JSON.toJSONString(list), type, parentName, e);
            }
        }


        return returnConvertByType(result, type, collectionName);
    }

    /**
     * 返回结果处理
     * @param result
     * @param type
     * @return
     */
    private static String returnConvertByType(String result, Byte type, String collectionName) {
        if (ObjectAdaptTypeEnum.OBJECT.getCode().equals(type)) {
            result = "{" + result + "}";
        } else if (ObjectAdaptTypeEnum.COLLECTION.getCode().equals(type)) {
            result = "[for(." + collectionName + "){" + result + "}]";
        }

        return result;
    }

    /**
     * 获取本次要处理的集合
     * @param list
     * @param type
     * @param parentName
     * @return
     */
    private static List<ObjectAdaptMappingInfoBO> getSelfList(List<ObjectAdaptMappingInfoBO> list, Byte type, String parentName) {
        if (CollectionUtils.isEmpty(list)) {
            return null;
        }
        // 如果parentName isBlank 直接返回所有
        if (StringUtil.isBlank(parentName)) {
            return list;
        }

        List<ObjectAdaptMappingInfoBO> selfList = new ArrayList<>();
        for (ObjectAdaptMappingInfoBO mappingInfoBO : list) {
            if (mappingInfoBO == null || mappingInfoBO.getTargetType() == null) {
                continue;
            }

            String targetColumn = mappingInfoBO.getTargetColumn();
            if (mappingInfoBO.getTargetType().equals(type) && targetColumn.startsWith(parentName + ".")) {
                ObjectAdaptMappingInfoBO mappingInfoBONew = new ObjectAdaptMappingInfoBO();
                BeanUtils.copyProperties(mappingInfoBO, mappingInfoBONew);
                // 把父去掉,生成新对象
                mappingInfoBONew.setTargetColumn(targetColumn.replaceFirst(parentName + ".", ""));
                selfList.add(mappingInfoBONew);
            }
        }
        return selfList;
    }

    /**
     * 将json字符串转为目标转换对象集合
     * @param str
     * @return
     */
    public static List<ObjectAdaptMappingInfoBO> jsonStrToTargetList(String str) {
        List<ObjectAdaptMappingInfoBO> list = new ArrayList<>();
        Map map = JSON.parseObject(str, Map.class);
        for (Object key : map.keySet()) {
            jsonStrConvertMappingBOList(list, (String) key, map.get(key), null);
        }
        return list;
    }

    /**
     * 将json字符串转为来源转换对象集合
     * @param str
     * @return
     */
    public static List<ObjectAdaptMappingInfoBO> jsonStrToSourceList(String str) {
        List<ObjectAdaptMappingInfoBO> mappingInfoBOList = jsonStrToTargetList(str);
        List<ObjectAdaptMappingInfoBO> result = new ArrayList<>();
        for (ObjectAdaptMappingInfoBO mappingInfoBO : mappingInfoBOList) {
            ObjectAdaptMappingInfoBO sourceMappingInfoBO = new ObjectAdaptMappingInfoBO();
            sourceMappingInfoBO.setSourceColumn(mappingInfoBO.getTargetColumn());
            result.add(sourceMappingInfoBO);
        }
        return result;
    }

    /**
     * 对象转换
     * @param list
     * @param parentName
     * @param value
     * @param type
     */
    private static void jsonStrConvertMappingBOList(List<ObjectAdaptMappingInfoBO> list, String parentName, Object value, Byte type) {
        if (value != null && JSON.class.isAssignableFrom(value.getClass())) {
            // 判断对象任然是JSON
            if (JSONObject.class.isAssignableFrom(value.getClass())) {
                JSONObject oo = (JSONObject) value;
                for (Map.Entry<String, Object> stringObjectEntry : oo.entrySet()) {
                    String key = stringObjectEntry.getKey();
                    Object value2 = stringObjectEntry.getValue();
                    String nextName = parentName == null ? key : parentName + "." + key;
                    if (JSONObject.class.isAssignableFrom(value2.getClass())) {
                        jsonStrConvertMappingBOList(list, nextName, value2, ObjectAdaptTypeEnum.OBJECT.getCode());
                    } else if (JSONArray.class.isAssignableFrom(value2.getClass())) {
                        jsonStrConvertMappingBOList(list, nextName, value2, ObjectAdaptTypeEnum.COLLECTION.getCode());
                    } else {
                        ObjectAdaptMappingInfoBO mappingInfoBO = new ObjectAdaptMappingInfoBO();
                        if (type == null) {
                            mappingInfoBO.setTargetType(ObjectAdaptTypeEnum.OBJECT.getCode());
                        } else {
                            mappingInfoBO.setTargetType(type);
                        }
                        mappingInfoBO.setTargetColumn(nextName);
                        addToList(list, mappingInfoBO);
                    }
                }
            } else if (JSONArray.class.isAssignableFrom(value.getClass())) {
                JSONArray oo = (JSONArray) value;
                for (Object o : oo) {
                    jsonStrConvertMappingBOList(list, parentName, o, ObjectAdaptTypeEnum.COLLECTION.getCode());
                }
            }
        } else {
            ObjectAdaptMappingInfoBO mappingInfoBO = new ObjectAdaptMappingInfoBO();
            if (type == null) {
                mappingInfoBO.setTargetType(ObjectAdaptTypeEnum.BASIC.getCode());
            } else {
                mappingInfoBO.setTargetType(type);
            }
            mappingInfoBO.setTargetColumn(parentName);
            addToList(list, mappingInfoBO);

        }
    }

    /**
     * 添加到集合,需要判断是否要可以添加
     * @param list
     * @param mappingInfoBO
     */
    private static void addToList(List<ObjectAdaptMappingInfoBO> list, ObjectAdaptMappingInfoBO mappingInfoBO) {
        Set<String> collect = list.stream().map(ObjectAdaptMappingInfoBO::getTargetColumn).collect(Collectors.toSet());
        if (collect.add(mappingInfoBO.getTargetColumn())) {
            list.add(mappingInfoBO);
        }
    }
}
View Code
JsltConvertUtilTest (测试方法) 
public class JsltConvertUtilTest {
    /**
     * | sourceColumn | targetColumn | type       |
     * | ------------ | ------------ | ---------- |
     * | a            | a1           | basic      |
     * | c            | b1.c1        | object     |
     * | d            | b1.d1        | object     |
     * | e            | e1           | basic      |
     * | g.ga         | g1.a2        | collection |
     * | g.gb         | g1.b2        | collection |
     *
     * 生成的jslt表达式:
     * {"a1":.a, "b1":{"c1":.c, "d1":.d}, "e1":.e, "g1":[for(.g){"a2":.ga, "b2":.gb}]}
     *
     * 原json:
     * {"a":"a","c":"c","d":"d","e":"e","g":[{"ga":"ga","gb":"gb"},{"ga":"ga2","gb":"gb2"},{"ga":"ga3","gb":"gb3"}]}
     *
     * jslt转换后json:
     * {"a1":"a","b1":{"c1":"c","d1":"d"},"e1":"e","g1":[{"a2":"ga","b2":"gb"},{"a2":"ga2","b2":"gb2"},{"a2":"ga3","b2":"gb3"}]}
     * @param args
     */
    public static void main(String[] args) {

        List<ObjectAdaptMappingInfoBO> list = new ArrayList<>();

        ObjectAdaptMappingInfoBO mappingInfoBO = new ObjectAdaptMappingInfoBO();
        mappingInfoBO.setSourceColumn("a");
        mappingInfoBO.setTargetColumn("a1");
        mappingInfoBO.setTargetType(ObjectAdaptTypeEnum.BASIC.getCode());
        list.add(mappingInfoBO);

        ObjectAdaptMappingInfoBO mappingInfoBO2 = new ObjectAdaptMappingInfoBO();
        mappingInfoBO2.setSourceColumn("c");
        mappingInfoBO2.setTargetColumn("b1.c1");
        mappingInfoBO2.setTargetType(ObjectAdaptTypeEnum.OBJECT.getCode());
        list.add(mappingInfoBO2);

        ObjectAdaptMappingInfoBO mappingInfoBO3 = new ObjectAdaptMappingInfoBO();
        mappingInfoBO3.setSourceColumn("d");
        mappingInfoBO3.setTargetColumn("b1.d1");
        mappingInfoBO3.setTargetType(ObjectAdaptTypeEnum.OBJECT.getCode());
        list.add(mappingInfoBO3);

        ObjectAdaptMappingInfoBO mappingInfoBO4 = new ObjectAdaptMappingInfoBO();
        mappingInfoBO4.setSourceColumn("e");
        mappingInfoBO4.setTargetColumn("e1");
        mappingInfoBO4.setTargetType(ObjectAdaptTypeEnum.OBJECT.getCode());
        list.add(mappingInfoBO4);

        ObjectAdaptMappingInfoBO mappingInfoBO5 = new ObjectAdaptMappingInfoBO();
        mappingInfoBO5.setSourceColumn("");
        mappingInfoBO5.setTargetColumn("f1");
        mappingInfoBO5.setTargetType(ObjectAdaptTypeEnum.OBJECT.getCode());
        list.add(mappingInfoBO5);

        ObjectAdaptMappingInfoBO mappingInfoBO6 = new ObjectAdaptMappingInfoBO();
        mappingInfoBO6.setSourceColumn("g.ga");
        mappingInfoBO6.setTargetColumn("g1.a2");
        mappingInfoBO6.setTargetType(ObjectAdaptTypeEnum.COLLECTION.getCode());
        list.add(mappingInfoBO6);

        ObjectAdaptMappingInfoBO mappingInfoBO7 = new ObjectAdaptMappingInfoBO();
        mappingInfoBO7.setSourceColumn("g.gb");
        mappingInfoBO7.setTargetColumn("g1.b2");
        mappingInfoBO7.setTargetType(ObjectAdaptTypeEnum.COLLECTION.getCode());
        list.add(mappingInfoBO7);

        System.out.println(JsltConvertUtil.convert(list, ObjectAdaptTypeEnum.OBJECT.getCode(), null));

        testStrToMapping();
    }

    /**
     * 测试str 转为 配置信息
     */
    private static void testStrToMapping() {
        String str = "{\"a1\":\"a\",\"b1\":{\"c1\":\"c\",\"d1\":\"d\",\"a3\":{\"t\":\"tt\"},\"a4\":[1,2,3]},\"e1\":\"e\",\"g1\":[{\"b2\":\"gb\"},{\"a2\":\"ga2\",\"b2\":\"gb2\"},{\"a2\":\"ga3\",\"b2\":\"gb3\"}]}";
        List<ObjectAdaptMappingInfoBO> mappingInfoBOList = JsltConvertUtil.jsonStrToTargetList(str);
        System.out.println(JSON.toJSONString(mappingInfoBOList));
    }
}
View Code

 

四.扩充方法

JSLT内置了许多方法,如果要自定义方法,可参考内置方法com.schibsted.spt.data.jslt.impl.BuiltinFunctions

然后修改执行代码:

        List<Function> functions = new ArrayList<>();
        functions.add(new FunctionTest2());
        functions.add(new FunctionTest());

        ObjectMapper mapper = new ObjectMapper();
        JsonNode input = mapper.readTree(inputStr);
        Expression jslt = Parser.compileString(jsltStr, functions);
        JsonNode output = jslt.apply(input);
        String s = output.toString();
        System.out.println(s);    

 如果有重名方法,会被最后添加的覆盖

 

posted @ 2020-12-28 17:31  登顶  阅读(2199)  评论(1编辑  收藏  举报