关于前后端JSON解析差异问题与思考

news2024/11/25 2:36:00

目录

一、问题回顾

二、问题思考

2.1 JavaScript如何解析json字符串

2.2 Java如何解析json字符串

2.3 Java和JavaScript交互如何出现JSON解析问题

三、经验总结


本文主要总结了作者在一次涉及流程表单的需求发布中遇到的问题及思考总结。

一、问题回顾

        在一次涉及流程表单的需求发布时,由于表单设计的改动,需要在历史工单中的一个json字段增加一个属性,效果示意如下:

[{"key1":"value1"}] ->  [{"key1":"value1", "key2":"value2"}]

        由于历史数据较多,采用了通过odc从数据库查询数据,线下开发数据处理脚本,更新数据后生成sql去线上执行,脚本示例如下。

String target = JSON.toJSONString(
    JSON.parseObject(oraData).put("key2","value2")
)

        在数据变更时未发现问题,存量工单抽查显示正常。但在第二天有业务反馈,有部分工单出现前端异常导致无法展示的问题。

        根据经验分析,这类问题一般是前端在处理数据时出现异常导致,考虑到昨天对工单中的一个json字段进行了变更,初步推断应该是该字段变更后影响了前端解析。

        为了验证猜测,将前端查询接口返回的数据,拷贝到在线解析json的网站中进行解析测试。结果发现页面解析失败,观察发现json中包含回车,回车删除后解析正常。在dev验证删除后数据解析正常后,应急对线上异常工单进行了处理。

二、问题思考

2.1 JavaScript如何解析json字符串

首先在Chrome控制台做一个简单的json解析实验,过程如下:

var str = "{\"key\":\"v1\\nv2\"}"
console.log(str)
// 打印结果 {"key":"v1\nv2"}
console.log(JSON.parse(str))
// 打印结果 {key: 'v1\nv2'}
console.log(JSON.parse(str).key)
// 打印结果 
// v1
// v2

经过测试,可以得到以下结论:

1)JavaScript在加载字符串时,会自动识别 斜杠 并进行反转义;

2)在进行json解析,会对值对应的字符串进行二次反转义,将\n解析为回车字符。

在查阅JSON官方对parse工具的流程说明后,印证了上述测试。

图片

        但在处理过程中,我们看到解释器的允许通过范围,有说明不包含control character。常见的回车、换行等字符都属于control character。

        所以可以推测,在JavaScript解析json时,如果遇到control character,可能会引起异常情况。

为了验证猜想做了以下对比实验。

图片

        经过测试可以发现,在JavaScript使用的JSON解析器sdk中,会对于字符串中的control character进行校验,如遇到则会直接抛出异常。由于JavaScript在加载字符串时会先做一次字符转义,会将”\n“转换为回车字符,即 (byte) 13,使得解析JSON时提示异常。

        这里会出现一个新的疑问,字符转义这个问题看起来是会经常出现的,为什么到现在才碰到一次呢。这里继续对JavaScript的JSON编码器进行测试。

图片

        经过测试可以发现,JavaScript在对字符串进行JSON编码时,会自动对字符串中的斜杠进行编码,正好对应了在JSON解码时对字符串自动解码。到这里可以明白了,同时仅使用JavaScript的JSON编码器和解码器,会自动对control character进行转义和反转义处理,不会出现异常。那在Java中呢?

2.2 Java如何解析json字符串

在java中,常用的JSON处理SDK是 fastjson,此处以 fastjson 进行测试。

为了搞清楚在java中 fastjson 会如何解析json字符串中的control character,进行了以下实验:

Map<String, String> testMap = new HashMap<>();
testMap.put("key", "v1\nv2");
System.out.println("1 >> " + JSON.toJSONString(testMap));
String testStr1 = "{\"key\":\"v1\nv2\"}";
System.out.println("2 >> " + testStr1);
System.out.println("3 >> " + JSON.parseObject(testStr1).getString("key"));
String testStr2 = "{\"key\":\"v1\\nv2\"}";
System.out.println("4 >> " + testStr2);
System.out.println("5 >> " + JSON.parseObject(testStr2).getString("key"));

打印结果如下:​​​​​​​

1 >> {"key":"v1\nv2"}
2 >> {"key":"v1
v2"}
3 >> v1
v2
4 >> {"key":"v1\nv2"}
5 >> v1
v2

经过测试,我们可以得出以下结论:

1)java的 fastjson 在进行json解析前,加载字符串时也会进行反转义处理,对于\n等control character也会转义为对应的byte值;

2)java的 fastjson 在对json中的字符串进行解码时,会将字符串中的转义符进行反转义处理;

3)java的 fastjson 在对json中的字符串进行解码时,不会受到字符串中control character的影响,可以正常提取值。

为了验证上面的推论,这里截取了fastjson关于解析String字符串的部分源码。​​​​​​​

public final void scanString() {
        np = bp;
        hasSpecial = false;
        char ch;
        for (;;) {
            ch = next();

            if (ch == '\"') {
                break;
            }

            if (ch == EOI) {
                if (!isEOF()) {
                    putChar((char) EOI);
                    continue;
                }
                throw new JSONException("unclosed string : " + ch);
            }
            
            if (ch == '\\') {
                if (!hasSpecial) {
                         hasSpecial = true;
                    if (sp >= sbuf.length) {
                        int newCapcity = sbuf.length * 2;
                        if (sp > newCapcity) {
                            newCapcity = sp;
                        }
                        char[] newsbuf = new char[newCapcity];
                        System.arraycopy(sbuf, 0, newsbuf, 0, sbuf.length);
                        sbuf = newsbuf;
                    }

                    copyTo(np + 1, sp, sbuf);
                    // text.getChars(np + 1, np + 1 + sp, sbuf, 0);
                    // System.arraycopy(buf, np + 1, sbuf, 0, sp);
                }

                ch = next();

                switch (ch) {
                    case '0':
                        putChar('\0');
                        break;
                    case '1':
                        putChar('\1');
                        break;
                    case '2':
                        putChar('\2');
                        break;
                    case '3':
                        putChar('\3');
                        break;
                    case '4':
                        putChar('\4');
                        break;
                    case '5':
                        putChar('\5');
                        break;
                    case '6':
                        putChar('\6');
                        break;
                    case '7':
                        putChar('\7');
                        break;
                    case 'b': // 8
                        putChar('\b');
                        break;
                    case 't': // 9
                        putChar('\t');
                        break;
                    case 'n': // 10
                        putChar('\n');
                        break;
                    case 'v': // 11
                        putChar('\u000B');
                        break;
                    case 'f': // 12
                    case 'F':
                        putChar('\f');
                        break;
                    case 'r': // 13
                        putChar('\r');
                        break;
                    case '"': // 34
                        putChar('"');
                        break;
                    case '\'': // 39
                        putChar('\'');
                        break;
                    case '/': // 47
                        putChar('/');
                        break;
                    case '\\': // 92
                        putChar('\\');
                        break;
                    case 'x':
                        char x1 = next();
                        char x2 = next();
                        boolean hex1 = (x1 >= '0' && x1 <= '9')
                                || (x1 >= 'a' && x1 <= 'f')
                                || (x1 >= 'A' && x1 <= 'F');
                        boolean hex2 = (x2 >= '0' && x2 <= '9')
                                || (x2 >= 'a' && x2 <= 'f')
                                || (x2 >= 'A' && x2 <= 'F');
                        if (!hex1 || !hex2) {
                            throw new JSONException("invalid escape character \\x" + x1 + x2);
                        }
                        char x_char = (char) (digits[x1] * 16 + digits[x2]);
                        putChar(x_char);
                        break;
                    case 'u':
                        char u1 = next();
                        char u2 = next();
                        char u3 = next();
                        char u4 = next();                        int val = Integer.parseInt(new String(new char[] { u1, u2, u3, u4 }), 16);
                        putChar((char) val);
                        break;
                    default:
                        this.ch = ch;
                        throw new JSONException("unclosed string : " + ch);
                }
                continue;
            }

            if (!hasSpecial) {
                sp++;
                continue;
            }

            if (sp == sbuf.length) {
                putChar(ch);
            } else {
                sbuf[sp++] = ch;
            }
        }

        token = JSONToken.LITERAL_STRING;
        this.ch = next();

    }

阅读源码后,我们可以验证我们的猜想,得出以下结论:

1)fastjson 在解析字符串时,会自动将遇到的转义字符进行反转义;

2)对于不需要特殊处理的字符,直接存入数据,继续处理下一字符。

2.3 Java和JavaScript交互如何出现JSON解析问题

        经过上面对 fastjson 和JavaScript在JSON处理的区别,可以清晰的看出来本次问题是如何出现的。

        起初在JavaScript进行JSON编码和解码时,会在编码时额外对\n等control character进行一次反转义。结果是在JavaScript对json字符串解码时,可以经过两次反转义再得到control character,不会触发校验引起意外情况。

原始数据:[{"key1","v1\\nv2"}]

        但是在需要追加数据时,由于数据加工处理是使用java脚本进行处理的,因此出现了编码差异。fastjson在解析JSON数据时,自动将\n等转义字符进行反转义,解析为control character。但是在加工处理后,将JSON数据编码为json字符串时,仅仅对control character进行了一次处理,还原为 \n 形式。

更新后数据:[{"key1","v1\nv2"},{"key2","value2"}]

        更新到数据库后,在前端查询解析时,由于在字符串加载时自动反转义出了control character,就会出现由于字符无法识别解码错误的问题。

        针对该问题,后续如果还有类似需求进行处理,需要编码得到数据后,对json字符串中的所有 \ 再次进行一次反转义,使得数据可以通过两次反转义再得到实际期望字符,即可解决该问题。

期望正确数据:[{"key1","v1\\nv2"},{"key2","value2"}]

三、经验总结

1)在需要进行数据处理的时候,尽量保持只有一方对数据进行加工和解析。如果具备条件则数据的编码、解码全部由后端处理;或者由于需求影响,全部由前端进行编码、解码,对于多个合作方使用了未知SKD编解码数据的场景也可以复用相同的原则,可以很大程度上避免该类问题发生。

2)在开发过程中,对于数据处理类sdk的升级、替换等操作需要慎之又慎。不同sdk的数据处理方式可能存在很多细小的差别,简单替换容易为日后出现事故埋下隐患。在进行升级、替换前一定要要针对与各类可能出现的数据情况进行充分验证。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/771919.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

FPGA驱动SPI屏幕(附完整工程)

一. 简介 相信大家都玩过屏幕&#xff0c;在FPGA上使用最多的就是VGA/HDMI接口的显示器了&#xff0c;这两种显示器的优点就不用说了&#xff0c;缺点就是体积比较大&#xff0c;而且价格比较贵&#xff0c;对于追求便携/价格低的我来说&#xff0c;SPI接口的屏幕才是我的首要…

实战操作接口自动化测试

最近接到一个接口自动化测试的case&#xff0c;并展开了一些调研工作&#xff0c;最后发现&#xff0c;使用pytest测试框架并以数据驱动的方式执行测试用例&#xff0c;可以很好的实现自动化测试。这种方式最大的优点在于后续进行用例维护的时候对已有的测试脚本影响很小。当然…

文件IO_文件截断_ftruncate,truncate(附Linux-5.15.10内核源码分析)

目录 1.为什么需要文件截断&#xff1f; 2.truncate函数介绍 2.1 truncate函数 2.2 truncate函数内核源码分析 2.3 truncate函数使用示例 3.ftruncate函数介绍 3.1 ftruncate函数 3.2 ftruncate函数内核源码分析 3.3 ftruncate函数使用示例 3.4 ftruncate和文件偏移量…

8年测试总结,App自动化测试-Appium常遇问题+解决(详细整理)

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 问题1&#xff1a…

小白入门C#编写MVC登录小案例

一、C#编写MVC登录小案例 &#x1f680;1. 新建MVC项目。 &#x1f680;2. 在Models文件夹下创建一个User类&#xff0c;包含登录所需要的用户名和密码属性。 namespace MvcLogin.Models {public class User{public string UserName{get; set;}public string Password{get;se…

基于Java+SpringBoot+Vue+Uniapp前后端分离考试学习一体机设计与实现(视频讲解,已发布上线)

博主介绍&#xff1a;✌全网粉丝3W&#xff0c;全栈开发工程师&#xff0c;从事多年软件开发&#xff0c;在大厂呆过。持有软件中级、六级等证书。可提供微服务项目搭建与毕业项目实战&#xff0c;博主也曾写过优秀论文&#xff0c;查重率极低&#xff0c;在这方面有丰富的经验…

计算机基础专升本笔记三 计算机中的数据表示、编码

计算机基础专升本笔记三计算机中的数据表示、编码 一、计算机中的数据的单位 &#xff08;一&#xff09;数据存储的单位有哪些&#xff1f; 计算机存储单位有 bit, Byte, KB, MB, GB, TB, PB, EB, ZB, BB来表示。我们经常将Byte简称为B&#xff0c;将KB简称K。 &#xff08…

微信怎么自动加好友,通过好友后自动打招呼

很多客户朋友每天花大量的时间用手机搜索添加好友&#xff0c;这样的添加很集中也容易频繁&#xff0c;而且效率还低。对方通过后&#xff0c;有时也不能及时和客户搭建链接&#xff0c;导致客户也流失了。 现在可以实现自动添加和自动打招呼哦&#xff0c;只需要导入数据、设置…

linux查看ipynb文件

linux查看ipynb文件 使用jupyter查看 使用jupyter查看 安装 pip install jupyter添加配置好的环境到jupyter notebook的kernel中&#xff1a; python -m ipykernel install --user --name mmdet --display-name "mmdet"运行jupyter notebook &#xff08;在ipynb…

精选了6款好用的AI绘画工具,值得一试

近几年来&#xff0c;伴随着AI技术的发展&#xff0c;设计领域发生了巨大的变化。AI绘图工具的出现很大程度上减轻了设计师的工作负担&#xff0c;本文精选了6款优秀的AI绘图工具为大家推荐&#xff0c;一起来看看吧&#xff01; 1、即时灵感 即时灵感作为国产的AI绘图工具&a…

相机标定学习笔记

Kalibr 是标定工具中&#xff0c;唯一一个可以标定camToImu的&#xff0c;是vio必不可少的工具&#xff0c;其他的都有替代品。所以学习多种开源算法进行相机标定&#xff0c;并记录学习相机标定的过程。 一、相机标定 1、在场景中放置一个已知的物体 &#xff08;1&#xff…

ENSP实验四:搭建VPN(GRE,配置安全策略)

首先分析一下数据的流向&#xff1a; PC1->PC2 1、FW1&#xff1a;trust->dmz 【192.168.1.1->192.168.2.1 ICMP】 2、AR1->AR2&#xff1a;【202.1.1.1->202.1.3.1|GRE|192.168.1.1->192.168.2.1 icmp】 3、FW2&#xff1a; ①untrust->local …

提示工程师:如何写好Prompt

提示工程由来 提示工程是一门相对较新的学科&#xff0c;用于开发和优化提示以有效地将语言模型 (LM) 用于各种应用程序和研究主题。 研究人员使用提示工程来提高 LLM 在广泛的常见和复杂任务&#xff08;例如问题回答和算术推理&#xff09;上的能力。 开发人员使用提示工程…

【图像处理OpenCV(C++版)】——5.6 图像平滑之联合双边滤波

前言&#xff1a; &#x1f60a;&#x1f60a;&#x1f60a;欢迎来到本博客&#x1f60a;&#x1f60a;&#x1f60a; &#x1f31f;&#x1f31f;&#x1f31f; 本专栏主要结合OpenCV和C来实现一些基本的图像处理算法并详细解释各参数含义&#xff0c;适用于平时学习、工作快…

用Vue如何实现低代码开发平台?

前言 在众多开发技术中&#xff0c;Vue组件化开发技术以其卓越的灵活性和高效性备受瞩目。 低代码平台相信不少人知道它的存在&#xff0c;而且现在大部分公司都在开发自己的低代码平台&#xff0c;首先我们来看看低代码平台可视化界面&#xff1a; 官网&#xff1a;https://ww…

UTM 4.3 发布:在 macOS 上优雅的使用 QEMU 虚拟化 Windows、Linux 和 macOS

UTM 4.3 发布&#xff1a;在 macOS 上优雅的使用 QEMU 虚拟化 Windows、Linux 和 macOS 在 iOS 中虚拟化 Windows、Linux 和 Unix 请访问原文链接&#xff1a;https://sysin.org/blog/utm-4/&#xff0c;查看最新版。原创作品&#xff0c;转载请保留出处。 作者主页&#xf…

Sql构建

Sql构建 SQL 构建对象介绍 之前通过注解开发时&#xff0c;相关 SQL 语句都是直接拼写的&#xff0c;一些关键字写起来比较麻烦、而且容易出错 MyBatis 提供了 org.apache.ibatis.jdbc.SQL 功能类&#xff0c;专门用于构建 SQL 语句 sql拼接测试&#xff1a; public class …

从制造到智造,安捷利的云数蝶变

伴随着新一轮科技革命和产业变革的兴起&#xff0c;制造业的数字化转型步入深水区&#xff0c;尤其是在5G、工业互联网、大数据等为代表的新技术推动下&#xff0c;制造业全方位、全链条的升级已是大势所趋。 南沙地处中国的南大门&#xff0c;既是国家面向世界的重要战略平台…

安达发|高级计划与智能排程APS软件的发展史进程

从泰勒的科学管理理论出发&#xff0c;率先追求科学的管理理论和管理工具&#xff0c;在计算机成为企业日常管理的基本工具之后&#xff0c;信息系统已经成为提高工厂管理水平的重要支柱。 在工厂计划领域&#xff0c;开始了从MRP到MRPII再到ERP的演变过程。MRPII指的是制造…

Appium+python自动化(十三)- 输入中文 - 一次填坑记(超详解)

简介 无论你在哪里&#xff0c;在做什么都会遇到很多坑&#xff0c;这些坑有些事别人挖的&#xff0c;有些是自己挖的。别人挖的叫坑人&#xff0c;自己挖的叫自杀&#xff0c;儿子挖的叫坑爹。因此在做app自动化道路上也不会是一帆风顺的&#xff0c;你会踩很多坑&#xff0c;…