前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >聊聊多层嵌套的json的值如何解析/替换

聊聊多层嵌套的json的值如何解析/替换

原创
作者头像
lyb-geek
发布2023-10-31 09:52:58
1.1K0
发布2023-10-31 09:52:58
举报
文章被收录于专栏:Linyb极客之路Linyb极客之路

前言

前阵子承接了2个需求,一个数据脱敏,一个是低代码国际化多语言需求,这两个需求有个共同特点,都是以json形式返回给前端,而且都存在多层嵌套,其中数据脱敏的数据格式是比较固定,而低代码json的格式存在结构固定和不固定 2种格式。最后不管是数据脱敏或者是多语言,业务抽象后,都存在需要做json值替换的需求。今天就来聊下多层嵌套json值如何解析或者替换

多层嵌套json解析

1、方法一:循环遍历+利用正则进行解析

这种做法相对常规,且解析比较繁琐。

2、方法二:利用OGNL表达式

1、何为OGNL

OGNL(Object-Graph Navigation Language)是一种表达式语言,用于在Java应用程序中对对象图进行导航和操作。OGNL本身并不提供直接的执行环境,它是作为一个库或框架的一部分来使用的。因此,OGNL的执行方式取决于使用它的上下文。

一般情况下,OGNL可以通过两种方式执行:解释执行和编译执行。

解释执行:在解释执行中,OGNL表达式在运行时逐条解释和执行。它会在每次表达式执行时动态计算表达式的结果,并根据对象图的实际状态进行导航和操作。这种方式的灵活性较高,可以根据需要对对象图进行动态操作,但相对而言执行效率较低。

编译执行:为了提高执行效率,有些框架会将OGNL表达式编译成可执行的字节码或类文件。在编译执行中,OGNL表达式在编译阶段被转换成可执行代码,然后在运行时直接执行这些生成的代码。这种方式可以在一定程度上提高执行速度,但牺牲了一些灵活性,因为编译后的代码在运行时不再动态计算。

官网:https://commons.apache.org/proper/commons-ognl/language-guide.html

我们经常使用ORM框架mybatis的动态sql解析,它的实现基石就是OGNL表达式。回到正题,我们如何利用OGNL来解析json

a、 在项目POM引入OGNL GAV

代码语言:java
复制
 <dependency>
            <groupId>ognl</groupId>
            <artifactId>ognl</artifactId>
            <version>${ognl.version}</version>
        </dependency>

b、 封装OGNL表达式工具类

代码语言:java
复制
public final class OgnlCache {

  private static final OgnlMemberAccess MEMBER_ACCESS = new OgnlMemberAccess();
  private static final OgnlClassResolver CLASS_RESOLVER = new OgnlClassResolver();
  private static final Map<String, Object> expressionCache = new ConcurrentHashMap<>();

  private OgnlCache() {
    // Prevent Instantiation of Static Class
  }

  public static Object getValue(String expression, Object root) {
    try {
      Map context = Ognl.createDefaultContext(root, MEMBER_ACCESS, CLASS_RESOLVER, null);
      return Ognl.getValue(parseExpression(expression), context, root);
    } catch (OgnlException e) {
      throw new RuntimeException("Error evaluating expression '" + expression + "'. Cause: " + e, e);
    }
  }

  private static Object parseExpression(String expression) throws OgnlException {
    Object node = expressionCache.get(expression);
    if (node == null) {
      node = Ognl.parseExpression(expression);
      expressionCache.put(expression, node);
    }
    return node;
  }

}

c、 封装json工具类

代码语言:java
复制
public final class JsonUtil {

    private JsonUtil(){}

    public static <T> T parse(String jsonStr, Class<T> clazz) throws Exception {
        return JSON.parseObject(jsonStr, clazz);
    }

    public static Object getValue(Map map, String path) throws Exception {
         return OgnlCache.getValue(path,map);
    }
}

d、 多层嵌套json解析例子

代码语言:java
复制
private void printMenuI18nCodeByOgnl() throws Exception {
        String menuJson = mockMenuService.getMenuJson();
        Map<String, Object> map = JsonUtil.parse(menuJson, Map.class);
        Object topMenu = JsonUtil.getValue( map,"i18NCode");
        Object userMenu = JsonUtil.getValue( map,"children[0].i18NCode");
        Object userMenuAdd = JsonUtil.getValue( map,"children[0].children[0].i18NCode");
        Object userMenuUpdate = JsonUtil.getValue( map,"children[0].children[1].i18NCode");
        Object deptMenu = JsonUtil.getValue( map,"children[1].i18NCode");
        Object deptMenuList = JsonUtil.getValue( map,"children[1].children[0].i18NCode");
        Object deptMenuDelete = JsonUtil.getValue( map,"children[1].children[1].i18NCode");
        System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> Print MenuI18nCode By Ognl Start <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<,");
        System.out.println(topMenu);
        System.out.println(userMenu);
        System.out.println(userMenuAdd);
        System.out.println(userMenuUpdate);
        System.out.println(deptMenu);
        System.out.println(deptMenuList);
        System.out.println(deptMenuDelete);
        System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> Print MenuI18nCode By Ognl End <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<,");
    }

注: 示例中的menuJson形如下

代码语言:text
复制
{"children":[{"children":[{"children":[],"component":"saas/index","i18NCode":"user.menu.add","id":8,"linkUrl":"/user/add","menuName":"用户新增","parentId":9,"sort":9999},{"children":[],"component":"saas/index","i18NCode":"user.menu.update","id":7,"linkUrl":"/user/update","menuName":"用户编辑","parentId":9,"sort":9999}],"component":"saas/index","i18NCode":"user.menu","id":9,"linkUrl":"/user","menuName":"用户菜单","parentId":1,"sort":9999},{"children":[{"children":[],"component":"saas/index","i18NCode":"dept.menu.list","id":11,"linkUrl":"/dept/list","menuName":"部门列表","parentId":10,"sort":9999},{"children":[],"component":"saas/index","i18NCode":"dept.menu.delete","id":12,"linkUrl":"/dept/delete","menuName":"部门删除","parentId":10,"sort":9999}],"component":"saas/index","i18NCode":"dept.menu","id":10,"linkUrl":"/dept","menuName":"部门菜单","parentId":1,"sort":9999}],"component":"saas/index","i18NCode":"top.menu","id":1,"linkUrl":"/topUrl","menuName":"顶级菜单","parentId":0,"sort":9999}

解析后控制台打印如下

代码语言:java
复制
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> Print MenuI18nCode By Ognl Start <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<,
top.menu
user.menu
user.menu.add
user.menu.update
dept.menu
dept.menu.list
dept.menu.delete
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> Print MenuI18nCode By Ognl End <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<,

OGNL表达式常用例子,可以查看如下链接

https://blog.51cto.com/rickcheung/238578

3、方法三:留个悬念,待会讲

多层嵌套json替换

1、方法一:循环遍历+正则进行替换

这种做法相对常规,且替换比较繁琐。

2、方法二:利用json类库,进行替换

以fastJSON为例

a、 在项目pom引入fastJSON GAV

代码语言:java
复制
 <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>${fastjson.version}</version>
        </dependency>

b、 多层嵌套json替换例子

以将菜单的i18nCode替换为具体语言的值为例

代码语言:java
复制
 public String reBuildMenuJson(){
        String orginalMenuJson = getMenuJson();
        JSONObject jsonObject = JSON.parseObject(orginalMenuJson);
        jsonObject.put(I18N_CODE_COLUMN,mockI18nCache.get(jsonObject.get(I18N_CODE_COLUMN)));
        reBuildChildJson(jsonObject);
        return JSON.toJSONString(jsonObject);

    }

    private void reBuildChildJson(JSONObject curentObject){
        JSONArray children = curentObject.getJSONArray(CHILDREN_COLUMN);
        for (int i = 0; i < children.size(); i++) {
            JSONObject child = children.getJSONObject(i);
            child.put(I18N_CODE_COLUMN,mockI18nCache.get(child.get(I18N_CODE_COLUMN)));
            reBuildChildJson(child);
        }

    }

注: 未替换前,menuJson形如下

代码语言:text
复制
{"children":[{"children":[{"children":[],"component":"saas/index","i18NCode":"user.menu.add","id":8,"linkUrl":"/user/add","menuName":"用户新增","parentId":9,"sort":9999},{"children":[],"component":"saas/index","i18NCode":"user.menu.update","id":7,"linkUrl":"/user/update","menuName":"用户编辑","parentId":9,"sort":9999}],"component":"saas/index","i18NCode":"user.menu","id":9,"linkUrl":"/user","menuName":"用户菜单","parentId":1,"sort":9999},{"children":[{"children":[],"component":"saas/index","i18NCode":"dept.menu.list","id":11,"linkUrl":"/dept/list","menuName":"部门列表","parentId":10,"sort":9999},{"children":[],"component":"saas/index","i18NCode":"dept.menu.delete","id":12,"linkUrl":"/dept/delete","menuName":"部门删除","parentId":10,"sort":9999}],"component":"saas/index","i18NCode":"dept.menu","id":10,"linkUrl":"/dept","menuName":"部门菜单","parentId":1,"sort":9999}],"component":"saas/index","i18NCode":"top.menu","id":1,"linkUrl":"/topUrl","menuName":"顶级菜单","parentId":0,"sort":9999}

替换后,menuJson形如下

代码语言:text
复制
{"component":"saas/index","children":[{"component":"saas/index","children":[{"component":"saas/index","children":[],"linkUrl":"/user/add","menuName":"用户新增","id":8,"sort":9999,"i18NCode":"userMenuAdd","parentId":9},{"component":"saas/index","children":[],"linkUrl":"/user/update","menuName":"用户编辑","id":7,"sort":9999,"i18NCode":"userUpdateAdd","parentId":9}],"linkUrl":"/user","menuName":"用户菜单","id":9,"sort":9999,"i18NCode":"userMenu","parentId":1},{"component":"saas/index","children":[{"component":"saas/index","children":[],"linkUrl":"/dept/list","menuName":"部门列表","id":11,"sort":9999,"i18NCode":"deptMenuList","parentId":10},{"component":"saas/index","children":[],"linkUrl":"/dept/delete","menuName":"部门删除","id":12,"sort":9999,"i18NCode":"deptMenuDelete","parentId":10}],"linkUrl":"/dept","menuName":"部门菜单","id":10,"sort":9999,"i18NCode":"deptMenu","parentId":1}],"linkUrl":"/topUrl","menuName":"顶级菜单","id":1,"sort":9999,"i18NCode":"topMenu","parentId":0}

3、方法三:利用json序列化注解

以菜单国际化为示例

1、自定义注解

代码语言:java
复制
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@JacksonAnnotationsInside
@JsonSerialize(using = I18nJsonSerializer.class)
public @interface I18nField {


}

2、自定义国际化翻译接口(该具体实现留给业务扩展)

代码语言:java
复制
public interface I18nService {

    String getTargetContent(String i18nCode);
}

题外话 : 为啥不像spring的messageSource定义成

代码语言:java
复制
	String getMessage(String code, @Nullable Object[] args, @Nullable String defaultMessage, Locale locale);

因为很多参数信息可以直接通过上下文获取,比如Locale可以通过LocaleContextHolder.getLocale()

3、编写json序列化接口

代码语言:java
复制
public class I18nJsonSerializer extends JsonSerializer<String> implements ContextualSerializer {

    @Autowired
    private I18nService i18nService;
    @Override
    public void serialize(String s, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
        jsonGenerator.writeString(i18nService.getTargetContent(s));

    }

    @Override
    public JsonSerializer<?> createContextual(SerializerProvider serializerProvider, BeanProperty beanProperty) throws JsonMappingException {

        I18nField i18nField = beanProperty.getAnnotation(I18nField.class);

        if(!ObjectUtils.isEmpty(i18nField) && String.class.isAssignableFrom(beanProperty.getType().getRawClass())){
            return this;
        }
        return serializerProvider.findValueSerializer(beanProperty.getType(),beanProperty);
    }
}

4、定义和json字段能够匹配的对象

大白话,就是json和这个对象可以相互转换。以菜单为例

代码语言:java
复制
@Data
@EqualsAndHashCode(callSuper = true, of = {"id"})
public class MenuResourceDTO extends TreeDTO<MenuResourceDTO> implements Serializable {

    private static final long serialVersionUID = 1L;

 。。。。。省略其他属性
    /**
     * 单菜名称
     */
    private String menuName;

  
    private String permission;
    /**
     * 是否缓存
     */
    private Integer keepAlive;

    @I18nField
    private String i18NCode;


    public static String I18N_CODE_COLUMN = "i18NCode";
    public static String CHILDREN_COLUMN = "children";

5、在需要进行替换的字段上加上 @I18nField注解

代码语言:java
复制
   @I18nField
    private String i18NCode;

6、替换验证

编写一个测试controller,用来输出替换后的菜单信息

代码语言:java
复制
@RestController
@RequestMapping("menu")
@RequiredArgsConstructor
public class MockMenuController {

    private final MockMenuService mockMenuService;


    @GetMapping
    public MenuResourceDTO getMenu(){
        return mockMenuService.getMenuResourceDTO();
    }
}

通过POSTMAN访问,得到如下信息

代码语言:javascript
复制
{
    "id": 1,
    "parentId": 0,
    "sort": 9999,
    "children": [
        {
            "id": 9,
            "parentId": 1,
            "sort": 9999,
            "children": [
                {
                    。。。省略其他信息
                    "menuName": "用户新增",
                    "i18NCode": "userMenuAdd"
                },
                {
                    。。。省略其他信息
                    "menuName": "用户编辑",
                    "i18NCode": "userUpdateAdd"
                }
            ],
            。。。省略其他信息
            "menuName": "用户菜单",
            "i18NCode": "userMenu"
        },
    "menuName": "顶级菜单",
    "i18NCode": "topMenu"
}

回答上面多层json解析的方法三,那个悬念做法就是将json与对象映射起来,通过对象来取值

4、方法四:先自己发散下,然后看下总结

总结

本文的多层嵌套json的解析和替换都提供了几种方案,综合来讲是推荐将json先转对象,通过对象操作。对json替换,推荐使用自定义json序列化注解的方式。但这种方式比较适合json的结构以及字段是固定的方式。对于低代码,本身的json结构是多种多样的,如果要后端实现,一种做法,就是将这些json都映射成对象,但因为json结构多种多样,就会导致要映射的对象膨胀。另一种方式,是直接转JsonObject,通过JsonObject来操作替换

其次现在都是前后端分离,有些东西其实也可以放在前端实现,比如这种替换工作其实挺适合放在前端做的。以低代码为例,因为前端本来就需要解析json,后端可以维护一个映射表,前端实现一个组件函数,通过该函数优先从前端缓存取,取不到再从调用后端接口,这就是json替换的方法四,把替换工作留给前端做,哈哈。大家是一个团队,哪边好实现,就放哪边做

最后那个ognl的代码,我是直接把mybatis的源码搬过来,直接套用了。开源有的东西,就没必要自己再搞一遍了

demo链接

https://github.com/lyb-geek/springboot-learning/tree/master/springboot-multinested-json-parse

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 多层嵌套json解析
    • 1、方法一:循环遍历+利用正则进行解析
      • 2、方法二:利用OGNL表达式
        • 3、方法三:留个悬念,待会讲
        • 多层嵌套json替换
          • 1、方法一:循环遍历+正则进行替换
            • 2、方法二:利用json类库,进行替换
              • 3、方法三:利用json序列化注解
                • 4、方法四:先自己发散下,然后看下总结
                • 总结
                • demo链接
                相关产品与服务
                数据脱敏
                数据脱敏(Data Masking,DMask)是一款敏感数据脱敏与水印标记工具,可对数据系统中的敏感信息进行脱敏处理并在泄漏时提供追溯依据,为企业数据共享、迁移、分发提供安全保护措施。
                领券
                问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档