flowable expression和json字符串中的双引号内容

news/2025/2/1 8:15:12 标签: json

前言

最近做项目,发现了一批特殊的数据,即特殊字符",本身输入双引号也不是什么特殊的字符,毕竟在存储时就是正常字符,只不过在编码的时候需要转义,转义符是\,然而转义符\也是特殊字符,又需要转义。这就造成了json字符串如果需要",需要很可能转义2次,即\\ \"。一般而言单层"只需要转义一次即可,但是json很可能再次嵌套,所以有时候需要多次转义,这里的\转义符在字符串也是有特殊含义的,毕竟内存存储"并不需要转义,这就出现了平时极难理解的情况。

这些其实还是比较好理解的,即:jvm内存的存储"并没有转义符,只不过编码需要,json字符串需要,负责json序列化和反序列化过程就是不可逆的。比如只能序列化,不能反序列化,但是如果Java执行groovy,或者执行了一些express表达式呢,这些数据会发生变化吗。

准备

准备demo,随意写一个对象,并且写一个groovy和express示例。

    <dependencies>
        <dependency>
            <groupId>org.codehaus.groovy</groupId>
            <artifactId>groovy-all</artifactId>
            <version>3.0.23</version>
            <type>pom</type>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>2.0.52</version>
        </dependency>
    </dependencies>

然后写一个groovy脚本

package org.example

def demo = binding.getVariable("demoBean")
println(demo.name)
demo.name = "demo"
demo.no = "000"
println(demo.name)

写一个bean(自行实现-忽略,普通javabean)和main测试类

public class Main {
    public static void main(String[] args) throws IOException {
        TestBean testBean = new TestBean();
        testBean.setNo("1213");
        testBean.setName("haha\"");
        Binding binding = new Binding();
        binding.setVariable("demoBean", testBean);
        GroovyShell groovyShell = new GroovyShell(binding);


        System.out.println("1. " + JSON.toJSONString(testBean));
        groovyShell.evaluate(new File("/Users/huahua/IdeaProjects/groovy-demo/src/main/java/org/example/demo.groovy"));

        Object object = groovyShell.getVariable("demoBean");
        System.out.println("2. " + JSON.toJSONString(object));

    }
}

这里要注意,Java执行groovyshell,需要的绑定关系是 new GroovyShell(binding),这里的binding命令会自动传递到groovy脚本,执行后如下

json

如上的结果分析:这里的json实际上有转义符,就说明了可读取的json实际上是解释形态,并不是编译运行形态,因为运行态的字符串实际上存放在常量池,是有转义符的,通过javap看看main类

javap -c -p -v Main.class 

但是如果字符串存入内存中,即运行态,并没有转义符,这就触发了问题的来源,json需要转义符,严格说是解释态需要,但是内存不需要,在传递的过程很可能出现丢失转义符导致json反序列化失败,因为json自身会对" { } 这些自身字符进行转义以区分是json字符串还是内容字符串,然而字符串的解释也需要,那就会出现再次转义的情况。

但是内存存储没有字符串的转义的说法,存储的原始信息

Grooovy脚本

笔者开始认为groovy脚本的操作可能会丢失\",如示例,因为实际生产有执行groovy,但是因为是内存操作,内存本身就没有转义符\

注释掉groovy对name的操作,可以看到实际上并没有任何变化,jvm内存本身就是没有转义符存储的,此时name字符串仅仅是对象的属性

json序列化后字符串

证明跟原始的数据没区别,转json后转义符\没丢失

express

后来发现对象经过了类似spring的@Value这样的表达式格式化后,出现了转义符丢失,毕竟javabean的值不是固定不变的,设置表达式,会在各个流程中进行格式化具体的表达式,从而动态的执行不同的参数和返回,计算本质就是输入 计算 输出。

模拟一个示例:最常见的就是工作流,在值传递时,实际上规则引擎,spring等都有express的能力,就以工作流为例bpmn2.0的expression

        <dependency>
            <groupId>org.flowable</groupId>
            <artifactId>flowable-engine-common</artifactId>
            <version>6.5.0</version>
        </dependency>

编写测试代码

public class Main {
    public static void main(String[] args) throws IOException {
        TestBean testBean = new TestBean();
        testBean.setNo("1213");
        testBean.setName("haha\"");

        Map<String, Object> map = new HashMap<>();
        map.put("testBean", testBean);
        map.put("testBean.name", "haha\"");
        map.put("testBean.no", testBean.getNo());

        ExpressionManager expressionManager = new DefaultExpressionManager();
        Expression expression = expressionManager.createExpression("{\"name\":\"${testBean.name}\",\"no\":\"${testBean.no}\"}");
        Object obj = expression.getValue(new VariableContainerWrapper(map));
        System.out.println(obj);
    }
}

然后执行

可以看到转义符消失,原因实际上已经很明确了express是表达式替换,并不是类似json的解释性格式,jvm内存是没有转义符的,Java本质上是解释性语言,所以class文件有转义符,所以"前的\转义符丢失,如果把这个json送给json反序列化一定报错,识别不了内容双引号的含义。

原因分析

org.flowable.common.engine.impl.de.odysseus.el.tree.impl.ast.AstComposite

其实是在这个示例中,使用字符串拼接的方式执行了替换,内存并没有相关的转义符,这里使用了bean的解析器,毕竟数据是从Javabean获取的

	public Object eval(Bindings bindings, ELContext context) {
		StringBuilder b = new StringBuilder(16);
		for (int i = 0; i < getCardinality(); i++) {
			b.append(bindings.convert(nodes.get(i).eval(bindings, context), String.class));
		}
		return b.toString();
	}

然后解析器实际上有多种

    public Object getValue(ELContext context, Object base, Object property) {
		context.setPropertyResolved(false);
		for (int i = 0, l = resolvers.size(); i < l; i++) {
			Object value = resolvers.get(i).getValue(context, base, property);
			if (context.isPropertyResolved()) {
				return value;
			}
		}
		return null;
	}

如果使用json解析器,那么是不是可以正常呢

 

看看json解析器的判断

懵,果然可以,但是这仅支持jackson,来试一下,确实可以了,但是结果依旧

public class Main {

    private static ObjectMapper reader = new ObjectMapper();

    public static void main(String[] args) throws IOException {
        TestBean testBean = new TestBean();
        testBean.setNo("1213");
        testBean.setName("haha\"");

        Map<String, Object> map = new HashMap<>();
        JsonNode jsonNode = reader.readTree("{\"name\":\"haha\\\"\",\"no\":\"1213\"}");
        map.put("testBean", jsonNode);
        map.put("testBean.name", "haha\"");
        map.put("testBean.no", testBean.getNo());

        ExpressionManager expressionManager = new DefaultExpressionManager();
        Expression expression = expressionManager.createExpression("{\"name\":\"${testBean.name}\",\"no\":\"${testBean.no}\"}");
        Object obj = expression.getValue(new VariableContainerWrapper(map));
        System.out.println(obj);
    }
}

看看结果 

根源还是因为express在flowable的工具类中是字符串拼接,且本身是字符串 

所以仅仅是支持了不同的输入对象逻辑罢了,最终结果并不会有任何变化

总结

通过示例可以看到字符串包括json需要对字符串的"内容进行转义,包括代码编写,class文件,但是jvm内存是不认"的转义符的,存储的就是真实的值,不存在转义的说法,而类似groovy脚本这样的类class语言实际上也是如此,毕竟操作在内存操作,class虚拟机不会有任何不同,毕竟class不一定能反编译Java,但是Java一定是编译为class,所以groovy并不会影响值操作的"结果。

关键点,我们会经常使用expression,不一定是工作流,以flowable为例,flowable支持各种输入传入,表达式也是标准的,但是expression的结果是字符串拼接的,不会考虑解释态,类似json这样的格式,所以输出的结果会丢弃转义符(实际上在字符串载入内存就丢弃了),expression仅仅是真实的还原内存数据,但是这确不是我们特定场景需要的结果,如果传给json反序列化bean就会报错。


http://www.niftyadmin.cn/n/5839185.html

相关文章

python高级编程涉及哪些内容

Python 高级编程涉及的内容广泛且深入&#xff0c;涵盖了从语言特性到设计模式的多个方面。以下是 Python 高级编程的主要内容&#xff1a; 1. 函数式编程 高阶函数&#xff1a;函数可以作为参数传递或返回&#xff0c;如 map、filter、reduce。Lambda 表达式&#xff1a;匿名…

计算机网络——流量控制

流量控制的基本方法是确保发送方不会以超过接收方处理能力的速度发送数据包。 通常的做法是接收方会向发送方提供某种反馈&#xff0c;如&#xff1a; &#xff08;1&#xff09;停止&等待 在任何时候只有一个数据包在传输&#xff0c;发送方发送一个数据包&#xff0c;…

前端面试笔试题目(一)

以下模拟了大厂前端面试流程&#xff0c;并给出了涵盖HTML、CSS、JavaScript等基础和进阶知识的前端笔试题目&#xff0c;以帮助你更好地准备面试。 面试流程模拟 1. 自我介绍&#xff08;5 - 10分钟&#xff09;&#xff1a;面试官会请你进行简单的自我介绍&#xff0c;包括…

【机器学习】自定义数据集 ,使用朴素贝叶斯对其进行分类

一、贝叶斯原理 贝叶斯算法是基于贝叶斯公式的&#xff0c;其公式为&#xff1a; 其中叫做先验概率&#xff0c;叫做条件概率&#xff0c;叫做观察概率&#xff0c;叫做后验概率&#xff0c;也是我们求解的结果&#xff0c;通过比较后验概率的大小&#xff0c;将后验概率最大的…

Vue.js组件开发-实现滑块滑动无缝切换和平滑切换动画

介绍如何使用 Vue 实现滑块滑动无缝切换和平滑切换动画 实现步骤 创建 Vue 项目&#xff1a;可以使用 Vue CLI 快速搭建一个新的 Vue 项目。设计 HTML 结构&#xff1a;创建一个包含滑块容器和滑块项的 HTML 结构。添加 CSS 样式&#xff1a;设置滑块容器和滑块项的样式&…

Java的Integer缓存池

Java的Integer缓冲池&#xff1f; Integer 缓存池主要为了提升性能和节省内存。根据实践发现大部分的数据操作都集中在值比较小的范围&#xff0c;因此缓存这些对象可以减少内存分配和垃圾回收的负担&#xff0c;提升性能。 在-128到 127范围内的 Integer 对象会被缓存和复用…

2025-1-26-sklearn学习(46) 无监督学习: 寻求数据表示 空伫立,尽日阑干倚遍,昼长人静。

文章目录 sklearn学习(46) 无监督学习: 寻求数据表示46.1 聚类: 对样本数据进行分组46.1.1 K-means 聚类算法46.1.2 分层聚类算法: 谨慎使用46.1.2.1 连接约束聚类46.1.2.2 特征聚集 46.2 分解: 将一个信号转换成多个成份并且加载46.2.1 主成份分析: PCA46.2.2 独立成分分析: I…

10.4 LangChain核心架构揭秘:模块化设计如何重塑大模型应用开发?

LangChain核心架构揭秘:模块化设计如何重塑大模型应用开发? 关键词: LangChain模块化设计、大模型开发框架、LangChain核心概念、AI应用开发、LLM工程化 一、LangChain的模块化设计哲学:从“手工作坊”到“工业化生产” 传统开发痛点: 代码重复:每个项目从零开始编写胶…