ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

ABAP sXML实现XML转JSON:数组识别与属性区分实战

ABAP sXML实现XML转JSON:数组识别与属性区分实战 做接口适配做得多了你会发现最耗时间的往往不是业务逻辑而是把一种数据形态搬到另一种数据形态。最近我在一个模拟项目X里处理外部系统回传的订单 XML前端死活只接受 JSON于是用 ABAP sXML 的流式读取能力手搓了一个 XML 转 JSON 的原型转换器把数组识别和属性区分这两个最容易翻车的问题单独拎出来做了专用处理。这篇文章把我当时的设计过程、关键代码和踩坑记录完整写下来给同样被 XML 接口折磨的人一个可以直接参考的原型。这个转换器解决的核心问题很朴素老系统还在出 XML新前端只认 JSON中间又没有人愿意维护 XSLT那就在 ABAP 层自己动手。适合 ABAP 后端开发、接口中间件维护者参考如果你只是偶尔转一次重点看第 3、4 节就够了。1. 为什么值得手搓一个 XML→JSON 转换器1.1 我遇到的真实场景换接口不如换转换器某个对接项目里外部系统长期吐 XML 采购订单结构长这样根节点下挂订单头订单头里有客户信息和条目清单条目清单下面再挂一到多条商品明细。之前我们用 XSLT 做映射前端改造后只接收 JSONXSLT 每条字段都要单独调映射规则XML 层级一变映射就崩调试一次要翻好几页样式表维护成本高得离谱。我当时的想法是既然 ABAP 一小时能做完接口为什么不能在 ABAP 里直接做格式转换真正动手之后才发现难点根本不在“XML 怎么读”而在“读出来之后怎么变成像样的 JSON”。尤其是数组识别同一个item节点在订单 A 里出现 8 次在订单 B 里可能只出现 1 次直接按 XML 树形结构拍成 JSON单条元素很容易变成普通对象前端拿不到数组页面上啥也渲染不出来。再加上属性区分item no1 qty2里的no和qty到底是应该和子元素平级还是应该塞进一个单独的 attributes 对象里不同团队有不同的偏好这也是前端经常扯皮的来源。所以我把它做成一个独立的转换器而不是散落在某个业务流程里的临时代码。目标很明确输入一段 XML 字符串输出一段 JSON 字符串中间过程不允许依赖第三方 JAR、不允许额外安装开源库纯 ABAP 自带能力完成。1.2 为什么选 sXML 而不是 DOMABAP 平台上解析 XML 一般走两条路一条是 DOM 风格把整个 XML 一次性载入内存构建成一棵节点树然后随便往树的任何一个位置取数据另一条是流式读取也就是 sXML 这一脉核心类就是cl_ixml_reader它像翻书一样一页一页地读每调用一次read_next就给你一个当前节点事件。我用生活例子给你类比DOM 是把一整本书复印一份放在桌上想查哪页就翻哪页方便但是书多的时候桌子不够用sXML 是你拿一支笔从头开始抄抄完一页就翻页手头永远只有当前这一页的内容内存占用非常克制。原型转换器面对的 XML 可能不大但接口上线后不一定谁知道对面系统哪天会吐一个几十兆的报文过来。DOM 在大报文场景下容易触发内存问题sXML 至少把这层风险压住了。有人会问sXML 是不是就只能顺序读一遍不能回头查数据理论上确实是顺序流但我们可以把读到的东西先存进自己的中间表后面再用自己的逻辑处理。原型里要做数组识别天然需要“看完整层兄弟节点才能决定字段类型”所以我把流程拆成两阶段先用 sXML 流式抽取出全部节点再做第二遍聚合。这样既享受了 sXML 的低内存读取又不牺牲算法灵活性。1.3 原型要做到什么程度才算可运行我给自己定的验收标准有五条你可以直接拿去当需求模板。第一能够解析一段合法的 XML 字符串并且返回一段合法 JSON 字符串能过 JSON 格式化工具校验。第二同名重复子元素能自动识别成 JSON 数组不要求理解 schema但至少要覆盖多数接口的常见形态。第三元素属性必须和子元素区分开我采用的约定是属性字段名加前缀下面会详细讲。第四文本内容、空元素、混合内容不能丢至少不能崩溃。第五解析失败要抛出可读的错误信息而不是 ABAP dump 一堆看不懂的短文本。满足这五条就算一个合格的原型。后面章节的代码就是按这个标准写的。2. 动手前必须理清三个设计点2.1 从 XML 节点流到 JSON 树结构映射规则XML 和 JSON 都是树状结构但表达方式不同。XML 里一个元素可以同时有属性、子元素、文本JSON 里一个对象就是一组键值对值只能是字符串、数字、布尔、数组或嵌套对象。所以不能把 XML 节点原样搬过去必须先定好一套映射规则否则每个接口都会冒出不同的 JSON 风格前端得天天陪你改代码。我用的映射规则非常简单一共四条。第一元素转换成 JSON 对象字段名就是元素名。第二元素的属性转换成以开头的字段比如order id1001变成id: 1001。第三元素的直接文本默认作为该字段的值如果这个元素既有属性又有子元素直接文本就落到#text字段里避免信息丢失。第四同一父节点下出现多个同名子元素时把这些子元素放到一个 JSON 数组里。举个例子。输入这段 XMLorder id1001 customer张三/customer item no1 qty2 skuA001/sku /item item no2 qty1 skuB002/sku /item /order按这套规则输出{ order: { id: 1001, customer: 张三, item: [ { no: 1, qty: 2, sku: A001 }, { no: 2, qty: 1, sku: B002 } ] } }这里item出现了两次所以被识别成数组。每条 item 里的no和qty就是属性sku是子元素。这套规则不复杂但足够支撑绝大多数业务 XML。2.2 数组识别同名兄弟节点出现几次才升数组数组识别是整个转换器里最容易想简单、也最容易做错的地方。我一开始想过一种粗暴方案不管出现几次只要在解析过程中发现某个名字已经有字段了第二次再遇到就把它变成数组。这样实现起来最快但会踩一个坑——同一份 XML 里同样的字段名可能在不同层级下出现多次语义完全不同。比如订单头和条目里都有remark订单头只有一条条目里有三条。如果只用“看到同名就升级数组”的全局逻辑订单头的remark也会被错误升级成数组前端直接拿错数据。正确的做法是数组判定必须基于“同一个父节点下的直接子元素”。也就是说每次统计的是父节点下有几个同名元素兄弟而不是整个 XML 文件里出现过几次。在第二阶段递归生成 JSON 时我按父节点 ID 分组先数一遍当前父节点下同名元素的数量数量大于 1就当作数组输出数量等于 1就当作普通对象字段输出。这个启发式规则有一个盲区有些 XML 语义上规定某个字段是数组但恰好这次只出现一条。比如history下理论上有多条record这次只回传了一条我的规则会把它输出成对象前端那条代码就会崩。原型阶段我用一个额外的强制数组名单来兜底调用时把这种字段名传进去即使只出现一条也按数组输出。这个名单在真实生产里往往来自接口文档的 maxOccurs 定义比纯靠猜可靠得多。2.3 属性区分用“前缀”还是“_attributes 包装”属性放到 JSON 里的方案行业里常见两种。第一种是把属性单独包成一个_attributes对象{ item: { _attributes: { no: 1, qty: 2 }, sku: A001 } }优点是属性就是属性子元素就是子元素结构上一眼能分清也不会出现奇怪的键名。缺点是嵌套多了一层前端每次取值都要先拆_attributes字段一多就非常啰嗦。第二种就是我在原型里用的前缀方案{ item: { no: 1, qty: 2, sku: A001 } }这样属性和子元素在同一个层级里键名自带标记。前端取属性的时候直接payload[no]后端拼数据的时候也不用额外维护一个容器节点。JSON 规范中字段名以开头是完全合法的实践中很多转换工具也这么干前端同学普遍能接受。我后来还加了一个小开关如果你实在不喜欢前缀可以把前缀改成任意字符串比如_或者干脆不加前缀。但默认还是推荐因为它和常见的 XML 属性语法之间有天然联想看代码不容易产生歧义。需要注意一个极端情况如果 XML 元素名本身以开头会和属性前缀撞车遇到这种结构要单独定义转义规则幸好现实中几乎碰不到。3. sXML 读取器核心 API 与最小 Demo3.1 sXML 读取模型事件流与节点类型sXML 读取器的工作方式说穿了就是死循环里不断调用read_next。每一次调用读取器内部状态会移到下一个节点然后你可以通过node_type属性判断当前节点到底是什么类型。常见的节点类型有这么几种元素开始节点、元素结束节点、文本节点、注释节点、处理指令节点另外还有表示“读取完毕”的空节点。元素开始节点是最关键的它代表一个 XML 标签的开始比如item no1。在这个节点上你不仅能拿到元素名还能拿到当前元素的所有属性。元素结束节点代表/item它告诉你当前元素的内容区结束了。文本节点就是标签之间夹着的文字内容比如customer张三/customer里的“张三”。注释节点和处理指令节点正常情况下直接丢弃。有一个体验上的提醒ABAP 的类常量名在不同内核版本里可能会有细微差别比如注释节点是node_type_comment处理指令是node_type_processing_instruction但你不需要死记硬背写代码的时候让 ABAP 自动补全顺着类常量列表找就行。核心思路是每个节点都有名字、类型、值三个最基本的属性类型决定你拿它做什么。3.2 最小 Demo把任意 XML 的节点序列打印出来刚开始写转换器的时候我建议你先做一个最小验证程序把 XML 里所有节点按顺序打印出来先跑通 sXML 读取链路再谈后续转换。下面这段代码就是干这个的你可以在任意测试程序里直接跑METHOD dump_xml_nodes. DATA(lo_ixml) cl_ixmlcreate( ). DATA(lo_factory) lo_ixml-create_stream_factory( ). DATA(lo_istream) lo_factory-create_istream_string( iv_xml ). DATA(lo_reader) cl_ixml_readercreate_from_input_stream( lo_istream ). DO. lo_reader-read_next( ). DATA(lv_node_type) lo_reader-node_type. IF lv_node_type if_ixml_readernode_type_none. EXIT. ENDIF. CASE lv_node_type. WHEN if_ixml_readernode_type_element. WRITE: / 元素开始, lo_reader-name. WHEN if_ixml_readernode_type_element_close. WRITE: / 元素结束, lo_reader-name. WHEN if_ixml_readernode_type_text. WRITE: / 文本, lo_reader-value. WHEN OTHERS. WRITE: / 其他节点, lo_reader-name. ENDCASE. ENDDO. ENDMETHOD.这段程序没有任何业务逻辑但特别适合验证你对 sXML 的理解。你拿一段真实接口的 XML 跑一遍会看到元素开始、元素结束、文本三种节点交替出现属性并不会作为独立节点跳出来它是挂在元素开始节点上的附属信息。这跟我前面说的第二阶段设计一致第一阶段只管把元素、文本、属性抽到中间表里不急着拼 JSON。4. 原型实现两阶段转换流程与完整代码4.1 数据结构定义第一阶段抽取完节点之后用什么结构存这些节点直接决定第二阶段好不好写。我设计了一张平铺的中间表每一行代表一个节点无关节点类型都能装TYPES: BEGIN OF ty_node, id TYPE i, parent TYPE i, name TYPE string, kind TYPE c LENGTH 1, value TYPE string, END OF ty_node.字段含义如下id是节点唯一编号第一阶段每抽到一个节点就自增parent是父节点的 id根元素父节点填 0name是节点名元素节点就是元素名属性节点就是属性名文本节点可以填空或填#textkind区分节点类型我约定E表示元素、A表示属性、T表示文本value存放值对元素来说通常是空对属性和文本来说就是它们的值。为什么要parent而不是树结构因为从 XML 转 JSON 时核心操作是“找出某个父节点下所有直接子节点再按名字分组”。父节点 id 配一个索引这种查询几乎白送比维护嵌套内表方便得多。4.2 第一阶段sXML 读取并填充中间表第一阶段的方法名叫parse_xml_to_nodes输入 XML 字符串输出一张节点中间表。这个阶段只负责把 XML 里所有有用信息抽出来不做任何 JSON 拼接。核心代码METHOD parse_xml_to_nodes. DATA(lo_ixml) cl_ixmlcreate( ). DATA(lo_factory) lo_ixml-create_stream_factory( ). DATA(lo_istream) lo_factory-create_istream_string( iv_xml ). DATA(lo_reader) cl_ixml_readercreate_from_input_stream( lo_istream ). DATA(lv_node_id) 1. DATA(ls_node) LIKE LINE OF mt_nodes. CLEAR: mt_nodes, mt_stack. DO. lo_reader-read_next( ). DATA(lv_node_type) lo_reader-node_type. IF lv_node_type if_ixml_readernode_type_none. EXIT. ENDIF. CASE lv_node_type. WHEN if_ixml_readernode_type_element. CLEAR ls_node. ls_node-id lv_node_id. ls_node-parent get_current_parent_id( ). ls_node-kind E. ls_node-name lo_reader-name. APPEND ls_node TO mt_nodes. 属性挂在元素开始节点上立即读取 DO lo_reader-attribute_count TIMES. lv_node_id lv_node_id 1. CLEAR ls_node. ls_node-id lv_node_id. ls_node-parent ls_node-parent. 指向上一个元素节点 id实际赋值看代码 ls_node-kind A. ls_node-name lo_reader-get_attribute_name( sy-index - 1 ). ls_node-value lo_reader-get_attribute_value( sy-index - 1 ). APPEND ls_node TO mt_nodes. ENDDO. APPEND lv_node_id TO mt_stack. lv_node_id lv_node_id 1. WHEN if_ixml_readernode_type_element_close. DELETE mt_stack INDEX lines( mt_stack ). WHEN if_ixml_readernode_type_text. CLEAR ls_node. ls_node-id lv_node_id. ls_node-parent get_current_parent_id( ). ls_node-kind T. ls_node-name #text. ls_node-value lo_reader-value. APPEND ls_node TO mt_nodes. lv_node_id lv_node_id 1. WHEN OTHERS. 注释、处理指令等直接忽略 ENDCASE. ENDDO. ENDMETHOD.这一段我故意没把所有细节写进一行重点是你看到两个关键动作遇到元素开始节点时马上循环读取它的属性遇到文本节点时把它和当前栈顶父节点关联起来。get_current_parent_id是读当前栈顶元素 id 的小方法栈里永远装着还未闭合的元素。属性读取有一个容易掉坑的地方一定要在元素开始节点这个位置立刻读过了这个节点读取器跑到文本或下一个元素去了再想拿属性就得回退流式接口通常不支持随意回退。这个时机不能错过我在后期调试时吃过亏。4.3 第二阶段统计数组并递归生成 JSON第二阶段的关键方法是build_object。它接收一个父节点 id返回从这个父节点视角看出去的 JSON 对象字符串。逻辑分四步先取出该父节点下所有直接子节点再把属性节点按name输出再把文本节点汇总成#text最后把元素节点按名字分组同名超过一个就组数组否则组普通对象。下面是方法骨架注释里写清了每段干什么METHOD build_object. DATA: lt_children TYPE STANDARD TABLE OF ty_node, lt_elems TYPE STANDARD TABLE OF ty_node, ls_child LIKE LINE OF lt_children, lv_json TYPE string, lv_name TYPE string, lv_group_count TYPE i. lt_children get_children_by_parent( iv_parent ). 第一步属性节点输出为 前缀字段 LOOP AT lt_children INTO ls_child WHERE kind A. IF ls_child-name CS xmlns. CONTINUE. ENDIF. lv_json lv_json ls_child-name : format_value( ls_child-value ) ,. ENDLOOP. 第二步直接文本节点汇总到 #text LOOP AT lt_children INTO ls_child WHERE kind T. lv_json lv_json #text: escape_json( ls_child-value ) ,. ENDLOOP. 第三步元素子节点按名字分组处理 SORT lt_children BY name. LOOP AT lt_children INTO ls_child WHERE kind E. lv_name ls_child-name. lv_group_count count_elements_by_name( iv_parent iv_parent iv_name lv_name ). IF lv_group_count 1 OR is_forced_array( lv_name ) abap_true. lv_json lv_json lv_name :[. LOOP AT lt_children INTO ls_child WHERE kind E AND name lv_name. lv_json lv_json build_value( ls_child-id ) ,. ENDLOOP. lv_json lv_json ],. ELSE. lv_json lv_json lv_name : build_value( ls_child-id ) ,. ENDIF. ENDLOOP. 去掉最后一个逗号包上大括号 IF strlen( lv_json ) 0. lv_json lv_json( strlen( lv_json ) - 1 ). ENDIF. rv_json { lv_json }. ENDMETHOD.build_value是判断当前元素最终输出成对象还是标量的方法。如果当前元素没有任何子元素和属性只有一段文本就按标量输出只要有子元素或属性就递归调用build_object生成嵌套对象。这个方法很短但对最终 JSON 形态影响很大METHOD build_value. DATA(lt_children) get_children_by_parent( iv_id ). IF lt_children IS INITIAL. 只有文本或空元素按标量输出 DATA(lv_text) get_direct_text( iv_id ). rv_json format_value( lv_text ). ELSE. rv_json build_object( iv_id ). ENDIF. ENDMETHOD.注意一个细节数组识别时我是先数完整个组再循环拼接不是见一个拼一个。这样能保证数组的方括号前后匹配也能避免在循环里反复修改正在遍历的内表实测下来结构最稳。4.4 转义、数字识别与特殊节点处理字符串拼接 JSON最容易翻车的是转义。XML 文本里出现的双引号、反斜杠、换行符如果不处理拼出来就是非法 JSON前端 JSON.parse 直接报错。我单独写了一个escape_json在输出任何文本值之前强制过一遍METHOD escape_json. rv_result iv_value. REPLACE ALL OCCURRENCES OF \ IN rv_result WITH \\. REPLACE ALL OCCURRENCES OF IN rv_result WITH \. REPLACE ALL OCCURRENCES OF cl_abap_char_utilitiesnewline IN rv_result WITH \n. REPLACE ALL OCCURRENCES OF cl_abap_char_utilitiescr_lf IN rv_result WITH \n. ENDMETHOD.然后是数字识别。XML 里所有值本质上都是字符串但 JSON 里前端经常希望qty是数字而不是2。我的format_value用了一个简单策略先尝试把字符串转成整数能转成功就不加引号转不成功就加引号输出。这个策略有两个妥协一是前导零会被丢掉比如编号007会变成数字 7二是小数精度可能出问题。所以我额外保留了一个开关调用方如果明确要求“所有值都当字符串”就走纯字符串分支。生产环境我强烈建议和业务确认清楚哪些字段必须保持字符串别让转换器瞎猜。特殊节点方面CDATA 内容在 sXML 读取时通常表现为文本节点所以走#text通道即可空元素a/a的直接文本是空字符串默认输出成如果你更希望输出null在format_value开头加一个空串判断就行。5. 边界情况、踩坑记录与调优建议5.1 常见问题速查表我把原型开发和联调阶段遇到的高频问题整理了一张表基本覆盖了你会撞上的坑问题现象处理方式同名字段在不同层级出现数组判定互相干扰必须按父节点 id 分组统计不用全局名字统计属性没有输出JSON 里只有子元素属性全丢了属性要在元素开始节点处立即读取不能等读完文本再回头取文本里有双引号或换行生成的 JSON 解析失败所有文本和属性值输出前统一走 escape_json空元素a/输出成空字符串前端类型不对在 format_value 里判断空串按配置输出 null 或空串XML 带 xmlns 命名空间键名带前缀且 xmlns 被当成属性排除所有 xmlns 开头属性键名暂时保留原始前缀从 HTTP 拿到的是 xstring直接传字符串给 sXML 乱码先用转换类按 XML 声明的代码页转成 Unicode 字符串只有一个元素但语义是数组输出对象导致前端崩加一个强制数组名单参数指定字段名按数组输出这张表不是理论推导是我一个坑一个坑踩出来的。尤其“读取属性时机”这条调试难度最高因为不报错只是 JSON 里悄悄少字段。5.2 实测踩坑记录命名空间、空元素、混合内容命名空间是我遇到的第一个大坑。某接口返回的 XML 长这样ns:order xmlns:nshttp://...。sXML 读取元素名的时候name属性拿到的是带前缀的ns:order。这意味着 JSON 键名会变成ns:order虽然前端不一定会立刻报错但看着非常奇怪。更糟的是xmlns:ns本身会被当成当前元素的一个属性如果不加排除JSON 里就会冒出一个xmlns:ns字段。我这里做了个简单处理所有以xmlns开头的属性直接丢弃键名暂时保留原名。真正生产环境如果要规范化命名空间需要按 prefix 到 URI 做一个映射再把键名替换成目标业务名称这一步工作量不小原型阶段可以不做。空元素是第二个坑。某接口里price/price表意是“价格为空”我直接输出成price: 。前端拿到空字符串再往里塞数字格式化函数页面就出现一个空的红色的 NaN。后来我把空元素输出改成可配置默认空字符串需要时输出 null。这个开关非常小但能救回不少前端 bug。混合内容是最难处理的类型。比如p说明b重点/b内容/p这个p元素既有直接文本“说明”和“内容”又有子元素b。如果不加#text机制直接文本会丢得一干二净。我后来在build_object里把所有直接子文本节点聚合到#text至少保证“说明”和“内容”还能在 JSON 里找到。注意聚合的时候要按顺序拼接不能随机乱序否则语义就错了。5.3 性能与调优建议原型跑起来之后我最担心的是大 XML。第一次拿一个 20 MB 的报文做压测sXML 读取本身很快但我的中间表存了 20 多万个节点再加上字符串用反复拼接内存涨得很明显。后来做了几件事效果立刻改善。第一中间表按 parent 建 SORTED TABLE。get_children_by_parent原本是遍历全表数据量大了以后明显卡改成按 parent 排序后每次只取出一个连续区间耗时几乎可以忽略。第二减少字符串拼接次数。build_object里每拼一个字段就做一次lv_json lv_json ...ABAP 字符串是不可变的这样每拼一次都要复制整个字符串。数据量小无所谓数据量大就是灾难。优化方式是用内表收集片段最后CONCATENATE LINES一次成型。原型代码为了可读性我保留了拼接写法但你要上生产这一步建议改掉。第三如果 XML 真的巨大两阶段方案也不是银弹。第一阶段虽然用 sXML 流式读取但中间表还是把全量节点都存了下来本质上是空间换时间的折中。如果要完全流式输出 JSON需要先做一遍“结构扫描”确认哪些字段是数组、哪些是标量再从头读一遍边读边输出。这个方案复杂度高不少原型阶段不推荐。6. 怎么把原型养大落到生产系统的要点6.1 封装成可复用的函数模块手搓原型最大的好处是逻辑在自己的掌控里但要长期用不能只留在测试程序里。我建议把转换器放进一个函数模块或全局类入参至少三个XML 字符串、强制数组字段名表、是否全部按字符串输出。出参就是 JSON 字符串。这样外部调用方完全不用关心 XML 结构只要传数据进来就返回 JSON。我就是这么封装的。函数模块里面做的事很纯粹解析iv_xml维护一个it_forced_arrays表然后调用convert返回ev_json。业务程序里调它就像调一个普通工具函数不会看到任何 sXML 的细节。如果要在 HTTP 接口里实时响应流程会稍微长一点。SICF 处理器里先取请求体的xstring按代码页转成字符串再调这个函数模块最后把 JSON 塞进response-set_cdata同时把 Content-Type 设置成application/json。这一步里最容易翻车的还是编码请求体不转码直接塞给 sXML中文字段会变成乱码前端拿到的 JSON 表面合法内容全错。6.2 什么时候该继续手搓什么时候换现成方案我不是那种“万物皆可手搓”的原教旨主义者。手搓转换器真正适合的场景有三个ABAP 环境里不方便引入外部 JSON 库XML 结构相对固定不会三天两头变团队里有人愿意维护这段代码。如果你的 XML 结构每天都在变或者接口数量多到爆炸我更建议花时间维护一套 XSLT或者推动上游系统直接输出 JSON。XSLT 做字段映射的声明式表达能力是硬编码转换器比不了的。但反过来说如果你只是接一两个老系统接口为了这个去引入一个重量级 XML 框架维护成本一样不低。原型转换器的定位本来就是“固定接口的专用适配器”不是“万能数据翻译机”。另外要泼一盆冷水数组识别和属性区分看似简单但其实每一项背后都有业务语义。你自己猜出来的规则必须经过业务人员确认尤其是数字转字符串、空元素 null、重复元素数组这三类属于前端后端最容易打架的点。最好的做法是在代码里把这些行为参数化让配置来决定而不是写死在算法里。6.3 说点掏心窝的话原型项目最终给我的启示这个原型项目让我想明白一件事格式转换的真正成本不在写代码而在“语义映射”。sXML 读取、JSON 拼接、转义处理这些都是成熟技术任何一个有经验的开发都能在一两天内写完。真正难的是定义规则什么算数组、属性放在哪层、数字要不要加引号。这些规则一旦定了代码反而只是把它翻译出来而已。所以如果你也要做类似转换器我最大的建议是先别急着写代码拿三份真实的接口报文把数组字段、属性字段、空值字段全部列出来找业务对一遍再动手。测试用例一定要包含“同一个字段只出现一次”和“出现多次”两种 XML因为很多数组 bug 就藏在这里。最后分享一个小技巧给转换器加一个“自检模式”允许用户传入一段样例 XML 和一个期望的 JSON 片段自动比对输出结果。这个自检模式在我后期每次调整算法时都帮了大忙比手动复制报文到 JSON 校验网站上反复试高效得多。希望这个手搓原型也能给你的接口适配工作省下几个通宵。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进