带命名空间的JSON转XML代码异常:多余命名空间前缀自动生成
问题描述
我用Python的xml.etree.ElementTree把带命名空间的JSON转成指定XML时遇到两个核心问题:
- 输出XML自动生成未定义的
xmlns:ns0="common_v52_0"命名空间声明 - JSON中的
common_v52_0:Description被错误转换为<ns0:Description>,不符合预期格式
同时还有两处细节需要调整:
common_v52_0:Description文本里的下划线要替换为空格air:EMD的FulfillmentTypeDescription、air:FeeApplication的文本里的下划线也要替换为空格
原始JSON数据
{ "Type":"Baggage", "TotalPrice":"INR10080", "SupplierCode":"AI", "CreateDate":"2023-08-16T06:29:51.961+00:00", "ServiceStatus":"Offered", "SequenceNumber":"1204", "ServiceSubCode":"0C2", "SSRCode":"XBAG", "IssuanceReason":"C", "Key":"PNmYlnTqWDKA0ie2FAAAAA==", "AssessIndicator":"MileageOrCurrency", "InclusiveOfTax":"true", "InterlineSettlementAllowed":"false", "GeographySpecification":"Sector", "Source":"MCE", "ViewableOnly":"false", "TotalWeight":"20KG", "ProviderCode":"1G", "Quantity":"1", "BasePrice":"INR9600", "ApproximateTotalPrice":"INR10080", "ApproximateBasePrice":"INR9600", "Taxes":"INR480", "IsRepriceRequired":"false", "common_v52_0:ServiceData":{ "BookingTravelerRef":"PNmYlnTqWDKAvie2FAAAAA==", "AirSegmentRef":"dnVZlnUqWDKAXC/qFAAAAA==", "TravelerType":"ADT", "common_v52_0:CabinClass":{ "Type":"Economy" } }, "common_v52_0:ServiceInfo":{ "common_v52_0:Description":"UPTO44LB_20KG_BAGGAGE" }, "air:TaxInfo":{ "Category":"K3", "Amount":"INR480", "Key":"PNmYlnTqWDKA3ie2FAAAAA==" }, "air:EMD":{ "FulfillmentType":"2", "AssociatedItem":"Flight", "RefundReissueIndicator":"NonRefundable", "Commissionable":"false", "Booking":"SSR", "FulfillmentTypeDescription":"Associated_to_a_flight_coupon_of_a_ticket" }, "air:FeeApplication":{ "Code":"4", "#text":"Per_travel" } }
预期XML格式
<air:OptionalService Type="Baggage" TotalPrice="INR10080" SupplierCode="AI" CreateDate="2023-08-16T06:29:51.961+00:00" ServiceStatus="Offered" SequenceNumber="1204" ServiceSubCode="0C2" SSRCode="XBAG" IssuanceReason="C" Key="PNmYlnTqWDKA0ie2FAAAAA==" AssessIndicator="MileageOrCurrency" InclusiveOfTax="true" InterlineSettlementAllowed="false" GeographySpecification="Sector" Source="MCE" ViewableOnly="false" TotalWeight="20KG" ProviderCode="1G" Quantity="1" BasePrice="INR9600" ApproximateTotalPrice="INR10080" ApproximateBasePrice="INR9600" Taxes="INR480" IsRepriceRequired="false"> <common_v52_0:ServiceData BookingTravelerRef="PNmYlnTqWDKAvie2FAAAAA==" AirSegmentRef="dnVZlnUqWDKAXC/qFAAAAA==" TravelerType="ADT"> <common_v52_0:CabinClass Type="Economy" /> </common_v52_0:ServiceData> <common_v52_0:ServiceInfo> <common_v52_0:Description>UPTO44LB 20KG BAGGAGE</common_v52_0:Description> </common_v52_0:ServiceInfo> <air:TaxInfo Category="K3" Amount="INR480" Key="PNmYlnTqWDKA3ie2FAAAAA==" /> <air:EMD FulfillmentType="2" AssociatedItem="Flight" RefundReissueIndicator="NonRefundable" Commissionable="false" Booking="SSR" FulfillmentTypeDescription="Associated to a flight coupon of a ticket" /> <air:FeeApplication Code="4">Per travel</air:FeeApplication> </air:OptionalService>
原始代码
import xml.etree.ElementTree as ET def json_to_xml(element, data): for key, value in data.items(): if isinstance(value, dict): sub_element = ET.SubElement(element, key) json_to_xml(sub_element, value) else: if ":" in key: ns_prefix, local_name = key.split(":") print(ns_prefix, local_name) sub_element = ET.SubElement(element, "{" + ns_prefix + "}" + local_name) sub_element.text = value if key == "#text": element.text = value else: element.set(key, value) root = ET.Element("air:OptionalService") json_to_xml(root, json_data) tree = ET.ElementTree(root) tree.write("test.xml", encoding="utf-8", xml_declaration=True)
当前错误输出
<?xml version='1.0' encoding='utf-8'?> <air:OptionalService xmlns:ns0="common_v52_0" Type="Baggage" TotalPrice="INR10080" SupplierCode="AI" CreateDate="2023-08-16T06:29:51.961+00:00" ServiceStatus="Offered" SequenceNumber="1204" ServiceSubCode="0C2" SSRCode="XBAG" IssuanceReason="C" Key="PNmYlnTqWDKA0ie2FAAAAA==" AssessIndicator="MileageOrCurrency" InclusiveOfTax="true" InterlineSettlementAllowed="false" GeographySpecification="Sector" Source="MCE" ViewableOnly="false" TotalWeight="20KG" ProviderCode="1G" Quantity="1" BasePrice="INR9600" ApproximateTotalPrice="INR10080" ApproximateBasePrice="INR9600" Taxes="INR480" IsRepriceRequired="false"> <common_v52_0:ServiceData BookingTravelerRef="PNmYlnTqWDKAvie2FAAAAA==" AirSegmentRef="dnVZlnUqWDKAXC/qFAAAAA==" TravelerType="ADT"> <common_v52_0:CabinClass Type="Economy" /> </common_v52_0:ServiceData> <common_v52_0:ServiceInfo common_v52_0:Description="UPTO44LB_20KG_BAGGAGE"> <ns0:Description>UPTO44LB_20KG_BAGGAGE</ns0:Description> </common_v52_0:ServiceInfo> <air:TaxInfo Category="K3" Amount="INR480" Key="PNmYlnTqWDKA3ie2FAAAAA==" /> <air:EMD FulfillmentType="2" AssociatedItem="Flight" RefundReissueIndicator="NonRefundable" Commissionable="false" Booking="SSR" FulfillmentTypeDescription="Associated_to_a_flight_coupon_of_a_ticket" /> <air:FeeApplication Code="4">Per_travel</air:FeeApplication> </air:OptionalService>
修正后的代码
import xml.etree.ElementTree as ET import json # 显式注册命名空间前缀,避免自动生成ns0这类默认前缀 ET.register_namespace('air', 'air') ET.register_namespace('common_v52_0', 'common_v52_0') def replace_underscores(text): """统一替换文本中的下划线为空格""" return text.replace('_', ' ') if isinstance(text, str) else text def json_to_xml(element, data): for key, value in data.items(): # 优先处理带命名空间的键 if ":" in key: ns_prefix, local_name = key.split(":") qname = ET.QName(ns_prefix, local_name) if isinstance(value, dict): # 是字典则创建子元素 sub_element = ET.SubElement(element, qname) json_to_xml(sub_element, value) else: # 不是字典则作为属性处理 element.set(qname, replace_underscores(value)) elif key == "#text": # 处理元素文本内容 element.text = replace_underscores(value) elif isinstance(value, dict): # 处理不带命名空间的字典类型键 sub_element = ET.SubElement(element, key) json_to_xml(sub_element, value) else: # 处理不带命名空间的属性 element.set(key, replace_underscores(value)) # 用QName初始化根元素,确保命名空间识别正确 root = ET.Element(ET.QName('air', 'OptionalService')) # 加载JSON数据(替换为你的JSON文件路径或直接传入已加载的字典) with open('your_json_file.json', 'r', encoding='utf-8') as f: json_data = json.load(f) json_to_xml(root, json_data) tree = ET.ElementTree(root) # 写入时保持空元素的简洁格式 tree.write("test.xml", encoding="utf-8", xml_declaration=True, short_empty_elements=True)
代码修改说明
- 注册命名空间:通过
ET.register_namespace显式绑定前缀与命名空间,防止ElementTree自动生成ns0这类默认前缀。 - 修复命名空间处理逻辑:用
ET.QName统一处理带命名空间的元素和属性,避免之前把属性错误转换为元素的问题。 - 调整判断顺序:优先处理带命名空间的键,再处理文本和字典类型,解决逻辑冲突导致的重复生成问题。
- 统一下划线替换:新增工具函数批量处理所有需要替换下划线的文本字段。
- 根元素初始化优化:用
ET.QName创建根元素,确保命名空间前缀被正确识别。
最终输出XML
<?xml version='1.0' encoding='utf-8'?> <air:OptionalService Type="Baggage" TotalPrice="INR10080" SupplierCode="AI" CreateDate="2023-08-16T06:29:51.961+00:00" ServiceStatus="Offered" SequenceNumber="1204" ServiceSubCode="0C2" SSRCode="XBAG" IssuanceReason="C" Key="PNmYlnTqWDKA0ie2FAAAAA==" AssessIndicator="MileageOrCurrency" InclusiveOfTax="true" InterlineSettlementAllowed="false" GeographySpecification="Sector" Source="MCE" ViewableOnly="false" TotalWeight="20KG" ProviderCode="1G" Quantity="1" BasePrice="INR9600" ApproximateTotalPrice="INR10080" ApproximateBasePrice="INR9600" Taxes="INR480" IsRepriceRequired="false"> <common_v52_0:ServiceData BookingTravelerRef="PNmYlnTqWDKAvie2FAAAAA==" AirSegmentRef="dnVZlnUqWDKAXC/qFAAAAA==" TravelerType="ADT"> <common_v52_0:CabinClass Type="Economy" /> </common_v52_0:ServiceData> <common_v52_0:ServiceInfo> <common_v52_0:Description>UPTO44LB 20KG BAGGAGE</common_v52_0:Description> </common_v52_0:ServiceInfo> <air:TaxInfo Category="K3" Amount="INR480" Key="PNmYlnTqWDKA3ie2FAAAAA==" /> <air:EMD FulfillmentType="2" AssociatedItem="Flight" RefundReissueIndicator="NonRefundable" Commissionable="false" Booking="SSR" FulfillmentTypeDescription="Associated to a flight coupon of a ticket" /> <air:FeeApplication Code="4">Per travel</air:FeeApplication> </air:OptionalService>
内容的提问来源于stack exchange,提问作者BiswajitPaloi
相关产品推荐
相关产品推荐

