ABAP开发者必备:JSON与XML数据格式转换实战指南

发布时间:2026/8/2 1:35:15
ABAP开发者必备:JSON与XML数据格式转换实战指南 1. 从业务数据到通用格式为什么ABAP开发者需要掌握JSON与XML在SAP ABAP的世界里我们打交道最多的就是内表、结构和工作区。这些数据在ABAP程序内部流转、处理、展示最终通过ALV报表、Smart Form或者直接写入数据库表。然而当系统需要与外部世界对话时——无论是调用一个互联网API、接收来自移动App的请求还是将数据提供给一个非SAP的报表工具——内表结构就不再是通用语言了。这时JSON和XML就成为了连接ABAP孤岛与外部广阔数字世界的桥梁。我见过不少ABAP开发者业务逻辑写得炉火纯青但一遇到需要对外提供接口或者解析外来数据文件就头疼。问题往往不是出在复杂的业务算法上而是卡在了最基础的格式转换上一个内表怎么变成JSON字符串发出去一个复杂的嵌套XML怎么解析成ABAP能理解的结构反过来从外部系统收到的一串JSON又如何优雅、安全地填充到我们精心设计的内表中这不仅仅是语法问题更关乎数据映射的思维转换、性能考量以及异常处理。简单来说JSON以其轻量、易读的特性已成为Web API和移动应用交互的事实标准而XML则凭借其严谨的结构和强大的 schema 支持在企业级系统集成、配置文件比如SAP自身的SPRO配置导出文件很多就是XML等领域依然占据重要地位。掌握这两种格式与ABAP内部数据结构的互转是现代ABAP开发者必备的“外功”。这能让你开发的程序不再局限于SAP GUI而是能够轻松融入更广阔的微服务架构或前后端分离的应用中。2. ABAP处理JSON从基础序列化到复杂结构映射JSON在ABAP中的处理核心是/UI2/CL_JSON这个类库。它是SAP官方推荐的标准方式替代了早期不太友好的CL_TREX_JSON_SERIALIZER等类。它的设计思路很清晰将ABAP数据对象结构、内表、简单类型序列化为JSON字符串或者将JSON字符串反序列化回ABAP数据对象。2.1 核心工具/UI2/CL_JSON类库详解这个类库主要通过两个静态方法提供核心功能SERIALIZE和DESERIALIZE。使用前你需要确保系统已经包含了这个类通常从SAP NetWeaver 7.40版本开始广泛支持。序列化 (ABAP - JSON)这是将ABAP数据转换为JSON字符串的过程。假设我们有一个简单的员工数据结构TYPES: BEGIN OF ty_employee, emp_id TYPE string, name TYPE string, department TYPE string, salary TYPE p DECIMALS 2, END OF ty_employee. DATA: ls_employee TYPE ty_employee, lv_json_string TYPE string. ls_employee-emp_id ‘E1001’. ls_employee-name ‘张三’. ls_employee-department ‘财务部’. ls_employee-salary ‘8500.50’. lv_json_string /ui2/cl_jsonserialize( data ls_employee ).执行后lv_json_string的内容会是{“EMP_ID”:”E1001”, “NAME”:”张三”, “DEPARTMENT”:”财务部”, “SALARY”:”8500.50”}这里有几个关键点需要注意字段名映射默认情况下ABAP结构字段的大写名称会直接作为JSON的键Key。例如EMP_ID。数据类型转换ABAP的字符串STRING、数字类型I, P, DECIMALS、日期D、时间T都能被正确转换。P类型带小数位的转换后会变成JSON number类型字符串。内表处理如果你传入的是一个内表比如DATA lt_employees TYPE TABLE OF ty_employee.SERIALIZE方法会自动将其序列化为JSON数组。反序列化 (JSON - ABAP)这是反向操作将JSON字符串解析并填充到ABAP变量中。DATA: lv_input_json TYPE string, ls_employee_target TYPE ty_employee. lv_input_json ‘{“emp_id”: “E1002”, “name”: “李四”, “salary”: 9200}’. /ui2/cl_jsondeserialize( EXPORTING json lv_input_json CHANGING data ls_employee_target ).执行后ls_employee_target中的emp_id,name,salary字段会被正确填充。department字段由于JSON中不存在则会保持初始值例如空值。注意反序列化时JSON的键名默认是大小写敏感的并且需要与ABAP结构字段名大写后匹配。如果JSON中使用的是驼峰命名如empId而ABAP结构是EMP_ID直接反序列化会失败因为键名不匹配。这就需要用到名称映射功能。2.2 处理复杂场景嵌套、数组与名称映射实际业务数据很少像上面那么简单。我们经常遇到嵌套结构和数组的数组。场景一嵌套对象例如员工信息里包含一个地址对象。TYPES: BEGIN OF ty_address, city TYPE string, street TYPE string, END OF ty_address. TYPES: BEGIN OF ty_employee_detail, emp_id TYPE string, name TYPE string, home_address TYPE ty_address, “嵌套结构 END OF ty_employee_detail. DATA: ls_emp_detail TYPE ty_employee_detail. ls_emp_detail-emp_id ‘E1003’. ls_emp_detail-name ‘王五’. ls_emp_detail-home_address-city ‘北京’. ls_emp_detail-home_address-street ‘海淀区中关村大街’.序列化后JSON会是{“EMP_ID”:”E1003”, “NAME”:”王五”, “HOME_ADDRESS”:{“CITY”:”北京”, “STREET”:”海淀区中关村大街”}}/UI2/CL_JSON能够自动处理这种嵌套将其转换为JSON对象。场景二名称映射Camel Case vs. 下划线外部系统API返回的JSON很可能使用驼峰命名法camelCase而ABAP字段习惯用大写加下划线SNAKE_CASE。硬编码修改ABAP结构字段名去适应JSON是不现实的。这时可以使用PRETTY_NAME参数。“假设JSON是{“empId”:”E1004”, “fullName”:”赵六”} TYPES: BEGIN OF ty_emp, emp_id TYPE string, full_name TYPE string, END OF ty_emp. DATA: ls_emp TYPE ty_emp. DATA(lv_json) ‘{“empId”:”E1004”, “fullName”:”赵六”}’. “方法1在反序列化时指定名称映射风格 /ui2/cl_jsondeserialize( EXPORTING json lv_json pretty_name /ui2/cl_jsonpretty_mode-camel_case “告诉解析器JSON键名是驼峰式 CHANGING data ls_emp ). “此时JSON的’empId’会自动映射到ABAP的’EMP_ID’字段。PRETTY_NAME参数非常强大它支持多种模式user_lower_case,camel_case,extended等能解决大部分命名风格不一致的问题。在序列化时使用相同的参数也能控制输出的JSON键名格式。场景三忽略未知字段与严格模式默认情况下DESERIALIZE会忽略JSON中存在但ABAP结构中不存在的字段。这通常是我们期望的行为保证了接口的向前兼容性。但有时为了数据完整性校验你可能需要开启严格模式让解析器在遇到未知字段时报错。DATA: lv_json TYPE string VALUE ‘{“empId”:”E1005”, “unknownField”:”someValue”}’. DATA: ls_emp TYPE ty_emp. “ty_emp没有unknownField字段 TRY. /ui2/cl_jsondeserialize( EXPORTING json lv_json pretty_name /ui2/cl_jsonpretty_mode-camel_case CHANGING data ls_emp ). CATCH cx_sy_move_cast_error INTO DATA(lx_error). “默认情况下这里不会进入unknownField被静默忽略 ENDTRY. “使用严格模式 /ui2/cl_jsondeserialize( EXPORTING json lv_json pretty_name /ui2/cl_jsonpretty_mode-camel_case ignore_boolean abap_false “不忽略布尔值相关转换不完全是严格模式… ). “实际上/UI2/CL_JSON没有直接的‘严格模式’参数。更严格的校验通常需要先解析到一个通用的JSON树结构如后面提到的/UI2/CL_JSONPARSE然后手动遍历赋值或者在反序列化后对比原始JSON键的集合。对于需要严格校验的场景一个更可靠的做法是使用/UI2/CL_JSONPARSE方法先将JSON解析成一个REF DATA指向一个动态生成的ABAP数据对象然后使用ASSIGN和字段符号FIELD-SYMBOLS来谨慎地访问数据这样可以完全控制赋值过程。2.3 性能考量与实战避坑指南在处理大批量数据时JSON序列化/反序列化可能成为性能瓶颈。以下是一些实战经验避免在循环中反复创建JSON解析器实例/UI2/CL_JSON的静态方法内部会处理实例化通常没问题。但如果你需要高度定制化的设置如自定义日期格式转换可以考虑创建并重用一个对象实例。谨慎处理极大字符串序列化一个包含数万行数据的内表生成的JSON字符串会非常庞大。直接操作这样的字符串可能消耗大量内存和CPU。如果可能考虑分页传输或流式处理。日期和时间格式JSON标准没有定义日期格式。/UI2/CL_JSON默认将ABAP日期类型D转换为YYYY-MM-DD格式的字符串时间类型T转换为HH:MM:SS。如果外部系统使用时间戳如2023-10-27T15:30:00Z你需要通过扩展转换器/UI2/CL_JSONSET_EXTENDED_CONVERTER或是在序列化前后手动进行格式转换。空值与初始值ABAP结构的初始值如空字符串‘’数字0在序列化成JSON时默认会被输出。有时外部系统期望忽略这些空值字段。你可以通过设置ASSOC_ARRAYS和BOOL_TYPES等参数来影响行为但更直接的方法是在序列化前清理内表或者使用/UI2/CL_JSON的COMPRESS模式如果支持或者在序列化后使用正则表达式移除空值项。Unicode与特殊字符确保你的ABAP字符串字段类型是STRING而非旧的CHAR类型以更好地支持Unicode。对于JSON字符串中的转义字符如\n,\t,\”/UI2/CL_JSON会自动处理。但如果JSON字符串本身格式错误如缺少引号解析时会抛出异常CX_SY_MOVE_CAST_ERROR务必用TRY…CATCH块包裹。3. ABAP处理XML解析、生成与XPath导航与JSON的轻量灵活相比XML更显严谨和强大常用于配置、报文如IDoc的XML版本、Web ServiceSOAP等场景。ABAP处理XML主要有两种范式基于DOM的解析和基于SAX的解析。对于大多数格式转换场景我们使用DOM方式因为它将整个XML文档读入内存形成一棵树状结构便于随机访问和修改。3.1 使用CL_XML_DOCUMENT进行DOM操作这是ABAP处理XML的经典类。它的工作流程是创建文档对象 - 解析XML字符串或流 - 获取根节点 - 遍历或查询节点 - 读取或修改数据 - 将文档渲染回字符串。解析XML (XML - ABAP 数据结构)假设我们有一个简单的XMLEmployee ID1001/ID Name张三/Name Skills SkillABAP/Skill SkillFiori/Skill /Skills /Employee我们的目标是将它解析到ABAP结构中TYPES: BEGIN OF ty_skill, skill_name TYPE string, END OF ty_skill. TYPES: ty_skills TYPE TABLE OF ty_skill WITH EMPTY KEY. TYPES: BEGIN OF ty_employee_xml, id TYPE string, name TYPE string, skills TYPE ty_skills, END OF ty_employee_xml. DATA: lo_xml_doc TYPE REF TO cl_xml_document, lv_xml_string TYPE string, ls_employee TYPE ty_employee_xml. lv_xml_string ‘Employee…/Employee’. “上述XML内容 “1. 创建并解析XML文档 CREATE OBJECT lo_xml_doc. lo_xml_doc-parse_string( lv_xml_string ). “2. 获取根节点 DATA(lo_root_node) lo_xml_doc-get_root_node( ). “3. 使用XPath查找节点并取值 DATA(lo_id_node) lo_root_node-find_from_name( name ‘ID’ ). IF lo_id_node IS BOUND. ls_employee-id lo_id_node-get_value( ). “得到 ‘1001’ ENDIF. DATA(lo_name_node) lo_root_node-find_from_name( name ‘Name’ ). IF lo_name_node IS BOUND. ls_employee-name lo_name_node-get_value( ). “得到 ‘张三’ ENDIF. “4. 处理重复节点列表 DATA(lo_skills_node) lo_root_node-find_from_name( name ‘Skills’ ). IF lo_skills_node IS BOUND. DATA(lt_skill_nodes) lo_skills_node-get_children( ). “获取Skills下所有子节点 LOOP AT lt_skill_nodes INTO DATA(lo_skill_node). IF lo_skill_node-get_type( ) co_node_type_element AND “确保是元素节点 lo_skill_node-get_name( ) ‘Skill’. APPEND VALUE #( skill_name lo_skill_node-get_value( ) ) TO ls_employee-skills. ENDIF. ENDLOOP. ENDIF.这个过程略显繁琐需要手动遍历节点树。对于结构固定的XML代码会显得重复。这时XPath可以极大地简化查询。3.2 利用XPath精准定位数据XPath是一种在XML文档中查找信息的语言。CL_XML_DOCUMENT通过IF_IXML_NODE接口的FIND_FROM_PATH方法支持XPath。沿用上面的XML例子我们可以用XPath一步到位地获取数据“查找ID节点值 DATA(lo_id_node) lo_root_node-find_from_path( path ‘/Employee/ID’ ). IF lo_id_node IS BOUND. ls_employee-id lo_id_node-get_value( ). ENDIF. “查找所有Skill节点的值直接填充内表 DATA(lt_skill_nodes) lo_root_node-find_from_path_all( path ‘/Employee/Skills/Skill’ ). LOOP AT lt_skill_nodes INTO DATA(lo_skill_node). APPEND VALUE #( skill_name lo_skill_node-get_value( ) ) TO ls_employee-skills. ENDLOOP.FIND_FROM_PATH_ALL方法非常有用它直接返回所有匹配节点的引用表省去了手动遍历子节点的步骤。XPath表达式如//Skill查找文档中所有Skill节点或/Employee/*查找Employee下所有直接子节点能处理更复杂的查询。注意XPath对节点名称大小写敏感。如果你的XML标签是ID那么XPath中也必须是ID写成id就找不到。3.3 生成与修改XML文档 (ABAP - XML)创建XML文档是反向过程。你需要从根节点开始一层层地创建元素节点并设置值。DATA: lo_xml_doc TYPE REF TO cl_xml_document, lo_root_node TYPE REF TO if_ixml_node, lo_element TYPE REF TO if_ixml_element. CREATE OBJECT lo_xml_doc. “1. 创建根元素 lo_root_node lo_xml_doc-create_element( name ‘Employee’ ). lo_xml_doc-set_root_node( lo_root_node ). “2. 创建并添加ID子元素 lo_element lo_xml_doc-create_element( name ‘ID’ ). lo_element-set_value( value ls_employee-id ). “假设ls_employee-id有值 lo_root_node-append_child( new_child lo_element ). “3. 创建并添加Name子元素 lo_element lo_xml_doc-create_element( name ‘Name’ ). lo_element-set_value( value ls_employee-name ). lo_root_node-append_child( new_child lo_element ). “4. 创建Skills容器元素及其子元素 DATA(lo_skills_node) lo_xml_doc-create_element( name ‘Skills’ ). lo_root_node-append_child( new_child lo_skills_node ). LOOP AT ls_employee-skills INTO DATA(ls_skill). lo_element lo_xml_doc-create_element( name ‘Skill’ ). lo_element-set_value( value ls_skill-skill_name ). lo_skills_node-append_child( new_child lo_element ). ENDLOOP. “5. 将XML文档渲染成字符串 DATA: lv_output_xml TYPE string. lo_xml_doc-render( OUTPUT lv_output_xml ).生成的lv_output_xml就是格式良好的XML字符串。你可以通过lo_xml_doc-set_encoding来设置编码如UTF-8通过lo_xml_doc-set_standalone来设置独立声明。3.4 处理命名空间与属性真实的XML尤其是SOAP报文或遵循特定标准的报文常常包含命名空间Namespace和属性Attributes。ns1:Employee xmlns:ns1“http://example.com/emp” id“1001” status“active” ns1:Name张三/ns1:Name /ns1:Employee处理这类XML时命名空间在创建元素或查找节点时需要指定命名空间URI和前缀。“创建带命名空间的元素 lo_element lo_xml_doc-create_element_ns( name ‘Name’, prefix ‘ns1’, uri ‘http://example.com/emp’ ). “使用XPath查找带命名空间的节点 DATA(lo_name_node) lo_root_node-find_from_path( path ‘/ns1:Employee/ns1:Name’ ). “注意使用find_from_path时需要确保文档上下文能识别这个命名空间前缀。 “更稳妥的方式是使用IF_IXML_NS_PREFIX_LIST来管理前缀。属性通过IF_IXML_ELEMENT接口的SET_ATTRIBUTE和GET_ATTRIBUTE方法读写。“设置属性 lo_element-set_attribute( name ‘id’, value ‘1001’ ). “读取属性 DATA(lv_status) lo_element-get_attribute( name ‘status’ ).处理带命名空间的XML是ABAP XML解析中最容易出错的地方之一。一个常见的坑是明明节点在那里XPath却找不到。这时请检查你的XPath是否包含了正确的前缀命名空间URI是否完全匹配包括末尾的斜杠是否需要在查找前先向文档或节点注册这个命名空间前缀4. 实战案例构建一个通用的格式转换工具函数理解了基础我们可以尝试构建一个更实用、更健壮的工具函数它能够处理常见的转换需求并封装好异常处理和日志。假设我们需要一个函数它能将任意ABAP结构/内表转换为指定格式JSON/XML的字符串并且能处理字段映射。4.1 设计思路与函数接口我们将创建一个函数模块或类方法。为了灵活性我们设计一个基于配置的转换器。输入IS_SOURCE_DATA: 任意ABAP数据结构或内表IV_TARGET_FORMAT: 目标格式‘JSON’或‘XML’IT_FIELD_MAPPING: 可选的字段映射表用于处理ABAP字段名与输出键名不一致的情况。例如ABAP字段EMP_ID映射为JSON的employeeId。IV_ROOT_NAME: 仅对XML有效指定根元素名称。输出EV_OUTPUT_STRING: 转换后的字符串。ET_RETURN: 返回消息表包含成功、警告或错误信息。核心逻辑根据IV_TARGET_FORMAT分支处理。JSON路径使用/UI2/CL_JSON利用PRETTY_NAME参数和IT_FIELD_MAPPING可能需要转换为NAME_MAPPINGS参数要求的格式进行序列化。XML路径使用CL_XML_DOCUMENT。首先需要一个将ABAP数据递归转换为XML节点树的子程序。这比JSON复杂因为XML需要显式创建元素层次。一个简化策略是将ABAP结构视为一个元素其每个字段成为一个子元素。内表则对应一个父元素下的一系列重复子元素。遍历IS_SOURCE_DATA的描述信息使用CL_ABAP_TYPEDESCR等RTTC相关类动态创建节点。整个操作包裹在TRY...CATCH中捕获所有可能的异常如转换错误、XML解析错误并转换为可读的消息填入ET_RETURN。4.2 关键实现细节动态类型处理与递归ABAP是强类型语言但我们的函数需要处理“任意”数据结构。这就要用到运行时类型服务RTTS。METHODS convert_to_xml IMPORTING ir_data TYPE REF TO data iv_root_name TYPE string DEFAULT ‘Root’ RETURNING VALUE(rv_xml) TYPE string RAISING cx_root. METHOD convert_to_xml. DATA: lo_xml_doc TYPE REF TO cl_xml_document, lo_root_node TYPE REF TO if_ixml_element. CREATE OBJECT lo_xml_doc. lo_root_node lo_xml_doc-create_element( name iv_root_name ). lo_xml_doc-set_root_node( lo_root_node ). “调用递归子程序构建节点树 build_xml_node( EXPORTING ir_current_data ir_data io_parent_node lo_root_node io_xml_document lo_xml_doc ). lo_xml_doc-render( OUTPUT rv_xml ). ENDMETHOD. METHOD build_xml_node. DATA: lo_type_descr TYPE REF TO cl_abap_typedescr, lo_struct_descr TYPE REF TO cl_abap_structdescr, lo_table_descr TYPE REF TO cl_abap_tabledescr, lt_components TYPE abap_component_tab, ls_component LIKE LINE OF lt_components. FIELD-SYMBOLS: ls_structure TYPE any, lt_table TYPE ANY TABLE, lv_field TYPE any. lo_type_descr cl_abap_typedescrdescribe_by_data_ref( ir_current_data ). CASE lo_type_descr-type_kind. WHEN cl_abap_typedescrtypekind_struct1 OR cl_abap_typedescrtypekind_struct2. “处理结构 lo_struct_descr ? lo_type_descr. lt_components lo_struct_descr-get_components( ). ASSIGN ir_current_data-* TO ls_structure. LOOP AT lt_components INTO ls_component. ASSIGN COMPONENT ls_component-name OF STRUCTURE ls_structure TO lv_field. IF sy-subrc 0. DATA(lo_element) io_xml_document-create_element( name ls_component-name ). “这里需要递归因为字段本身可能又是一个结构或内表 DATA(lr_field_ref) GET_REFERENCE_OF( lv_field ). “获取字段的引用 build_xml_node( EXPORTING ir_current_data lr_field_ref io_parent_node lo_element io_xml_document io_xml_document ). io_parent_node-append_child( lo_element ). ENDIF. ENDLOOP. WHEN cl_abap_typedescrtypekind_table. “处理内表 lo_table_descr ? lo_type_descr. ASSIGN ir_current_data-* TO lt_table. LOOP AT lt_table ASSIGNING ls_structure. DATA(lo_row_element) io_xml_document-create_element( name ‘Row’ ). “或使用表名 DATA(lr_row_ref) GET_REFERENCE_OF( ls_structure ). build_xml_node( EXPORTING ir_current_data lr_row_ref io_parent_node lo_row_element io_xml_document io_xml_document ). io_parent_node-append_child( lo_row_element ). ENDLOOP. WHEN OTHERS. “处理简单类型字符串、数字、日期等 io_parent_node-set_value( value |{ ir_current_data-* }| ). “转换为字符串 ENDCASE. ENDMETHOD.这是一个高度简化的递归构建逻辑实际应用中还需要处理更多细节日期/时间格式转换、避免循环引用、处理初始值是否输出为空标签、添加属性等。但它展示了核心思想利用RTTS动态分析数据类型并递归地构建XML树。4.3 错误处理与日志记录在格式转换中错误处理至关重要。我们的工具函数应该能优雅地处理以下问题JSON/XML格式错误无法解析。数据类型不匹配例如JSON的字符串无法转换为ABAP的数值类型。字段映射失败。递归深度过大防止栈溢出。在TRY...CATCH块中我们应该捕获具体的异常类如CX_SY_MOVE_CAST_ERROR类型转换错误、CX_TRANSFORMATION_ERROR某些转换类抛出以及通用的CX_ROOT。捕获后不应仅仅抛出一个ABAP短 dump而应将错误信息异常文本、可能出错的字段或路径记录到ET_RETURN中并设置恰当的消息类型E代表错误W代表警告I代表信息。TRY. “核心转换逻辑 CASE iv_target_format. WHEN ‘JSON’. ev_output_string /ui2/cl_jsonserialize( data is_source_data name_mappings lt_mappings ). WHEN ‘XML’. ev_output_string convert_to_xml( ir_data lr_source_data iv_root_name iv_root_name ). WHEN OTHERS. RAISE EXCEPTION TYPE cx_dynamic_check MESSAGE ‘不支持的输出格式’. ENDCASE. “添加成功消息到ET_RETURN APPEND VALUE bapiret2( type ‘S’ id ‘ZMSG’ number ‘001’ message_v1 iv_target_format ) TO et_return. CATCH cx_sy_move_cast_error INTO DATA(lx_cast). “处理类型转换错误 APPEND VALUE bapiret2( type ‘E’ id ‘ZMSG’ number ‘002’ message lx_cast-get_text( ) ) TO et_return. RETURN. “或抛出业务异常 CATCH cx_root INTO DATA(lx_other). “捕获其他所有异常 APPEND VALUE bapiret2( type ‘E’ id ‘ZMSG’ number ‘003’ message lx_other-get_text( ) ) TO et_return. RETURN. ENDTRY.4.4 性能优化与缓存策略对于被频繁调用的结构每次转换都进行RTTS分析和动态节点创建开销很大。可以考虑引入缓存机制缓存类型描述符对于已知的、固定的ABAP类型可以在程序初始化时获取其类型描述对象CL_ABAP_TYPEDESCR并缓存起来避免每次转换都调用DESCRIBE_BY_DATA。缓存映射规则如果字段映射规则IT_FIELD_MAPPING是固定的可以为其生成一个唯一的配置ID并缓存对应的处理逻辑例如预处理好的NAME_MAPPINGS表。对于XML如果结构固定可以预先创建一个XML文档模板只包含节点结构没有值每次转换时复制这个模板然后只填充值。这比从头创建每个节点要快得多。CL_XML_DOCUMENT的CLONE方法或IF_IXML_NODE的APPEND_CHILD从另一个文档复制节点可以用于此目的。构建这样一个工具函数本身就是一个复杂的项目但它一旦完成将成为团队中所有接口开发人员的利器极大提升开发效率和代码质量。它把格式转换的复杂性封装在黑盒里让业务开发者可以更专注于业务逻辑本身。