如何利用jq、yq、xq将JSON/YAML转换为与原始XML结构一致的XML文件?
解决XML与JSON/YAML互转时的格式还原问题
这个问题我之前也碰到过,核心原因是你使用的yq 3.0.2版本比较老旧,它没有内置识别xq生成的特殊字段(@前缀代表XML属性、#text代表元素文本内容)的逻辑,所以直接转换会把这些字段当成普通子元素。下面给你两种可行的解决方案:
方案一:升级到mikefarah的yq v4+(推荐)
新版本的yq(v4.x及以上)和xq同属一个工具套件,原生支持xq的反向转换逻辑,能自动把@开头的字段解析为XML属性,#text解析为元素文本。
安装方式(Fedora 36)
你可以直接通过包管理器安装最新版:
sudo dnf install yq
如果dnf提供的版本不是v4,也可以下载官方编译好的二进制包替换旧版本。
测试转换
针对你的额外测试示例:
echo '<ele attr_name="attr_value">ele_value</ele>' | xq | yq -o=xml
会输出正确的XML结构:
<ele attr_name="attr_value">ele_value</ele>
针对你原始的JSON文件,直接执行:
yq -o=xml json_file
就能还原出带命名空间、属性的原始XML结构,比如security-settings的xmlns属性、security-setting的match属性,以及permission的type和roles属性都会正确保留。
方案二:用jq预处理JSON(适配旧版yq)
如果暂时无法升级yq,你可以通过jq编写过滤器,把xq生成的特殊字段转换成旧版yq能识别的XML结构。
编写jq过滤脚本
创建一个名为xmlify.jq的文件,内容如下(递归处理所有属性和文本):
def xmlify: if type == "object" then # 提取文本内容 (if has("#text") then .["#text"] else null end) as $text # 处理所有字段:@开头的转为属性,其他转为子元素 | (del(."#text") | to_entries | map( if .key | startswith("@") then {"attr": (.key[1:], .value)} else {"elem": (.key, (.value | xmlify))} end )) as $nodes # 拆分属性和子元素 | ($nodes | map(select(has("attr"))) | map({key: .attr[0], value: .attr[1]}) | from_entries) as $attrs | ($nodes | map(select(has("elem"))) | map({(.elem[0]): .elem[1]})) as $elems # 组合成yq能识别的结构 | if $text != null then $elems[0] + {$attrs} + {"#text": $text} else $elems[0] + {$attrs} end elif type == "array" then map(xmlify) else . end; xmlify
执行转换
用jq预处理JSON后再用yq转XML:
cat json_file | jq -f xmlify.jq | yq -o=xml -P
这样就能生成和原始XML结构一致的输出。
内容的提问来源于stack exchange,提问作者Gullah Geechee




