json - 将 JSON 转换为 XML 的 T-SQL 函数
问题描述
我需要将此 JSON 数据转换为 XML:
https://api.exchangeratesapi.io/history?start_at=2019-07-20&end_at=2019-07-26&base=CAD&symbols=USD
我正在使用下面代码中显示的函数,我从这里得到:
https://sqlsunday.com/2013/05/12/converting-json-data-to-xml-using-at-sql-function/
但它不起作用,我收到如下所示的错误消息。
需要对函数中的代码进行哪些修改,以便将上述 JSON 数据转换为 XML?
这是我尝试过的功能:
CREATE FUNCTION dbo.fn_parse_json2xml(
@json varchar(max)
)
RETURNS xml
AS
BEGIN;
DECLARE @output varchar(max), @key varchar(max), @value varchar(max),
@recursion_counter int, @offset int, @nested bit, @array bit,
@tab char(1)=CHAR(9), @cr char(1)=CHAR(13), @lf char(1)=CHAR(10);
--- Clean up the JSON syntax by removing line breaks and tabs and
--- trimming the results of leading and trailing spaces:
SET @json=LTRIM(RTRIM(
REPLACE(REPLACE(REPLACE(@json, @cr, ''), @lf, ''), @tab, '')));
--- Sanity check: If this is not valid JSON syntax, exit here.
IF (LEFT(@json, 1)!='{' OR RIGHT(@json, 1)!='}')
RETURN '';
--- Because the first and last characters will, by definition, be
--- curly brackets, we can remove them here, and trim the result.
SET @json=LTRIM(RTRIM(SUBSTRING(@json, 2, LEN(@json)-2)));
SELECT @output='';
WHILE (@json!='') BEGIN;
--- Look for the first key which should start with a quote.
IF (LEFT(@json, 1)!='"')
RETURN 'Expected quote (start of key name). Found "'+
LEFT(@json, 1)+'"';
--- .. and end with the next quote (that isn't escaped with
--- and backslash).
SET @key=SUBSTRING(@json, 2,
PATINDEX('%[^\\]"%', SUBSTRING(@json, 2, LEN(@json))+' "'));
--- Truncate @json with the length of the key.
SET @json=LTRIM(SUBSTRING(@json, LEN(@key)+3, LEN(@json)));
--- The next character should be a colon.
IF (LEFT(@json, 1)!=':')
RETURN 'Expected ":" after key name, found "'+
LEFT(@json, 1)+'"!';
--- Truncate @json to skip past the colon:
SET @json=LTRIM(SUBSTRING(@json, 2, LEN(@json)));
--- If the next character is an angle bracket, this is an array.
IF (LEFT(@json, 1)='[')
SELECT @array=1, @json=LTRIM(SUBSTRING(@json, 2, LEN(@json)));
IF (@array IS NULL) SET @array=0;
WHILE (@array IS NOT NULL) BEGIN;
SELECT @value=NULL, @nested=0;
--- The first character of the remainder of @json indicates
--- what type of value this is.
--- Set @value, depending on what type of value we're looking at:
---
--- 1. A new JSON object:
--- To be sent recursively back into the parser:
IF (@value IS NULL AND LEFT(@json, 1)='{') BEGIN;
SELECT @recursion_counter=1, @offset=1;
WHILE (@recursion_counter!=0 AND @offset<LEN(@json)) BEGIN;
SET @offset=@offset+
PATINDEX('%[{}]%', SUBSTRING(@json, @offset+1,
LEN(@json)));
SET @recursion_counter=@recursion_counter+
(CASE SUBSTRING(@json, @offset, 1)
WHEN '{' THEN 1
WHEN '}' THEN -1 END);
END;
SET @value=CAST(
dbo.fn_parse_json2xml(LEFT(@json, @offset))
AS varchar(max));
SET @json=SUBSTRING(@json, @offset+1, LEN(@json));
SET @nested=1;
END
--- 2a. Blank text (quoted)
IF (@value IS NULL AND LEFT(@json, 2)='""')
SELECT @value='', @json=LTRIM(SUBSTRING(@json, 3,
LEN(@json)));
--- 2b. Other text (quoted, but not blank)
IF (@value IS NULL AND LEFT(@json, 1)='"') BEGIN;
SET @value=SUBSTRING(@json, 2,
PATINDEX('%[^\\]"%',
SUBSTRING(@json, 2, LEN(@json))+' "'));
SET @json=LTRIM(
SUBSTRING(@json, LEN(@value)+3, LEN(@json)));
END;
--- 3. Blank (not quoted)
IF (@value IS NULL AND LEFT(@json, 1)=',')
SET @value='';
--- 4. Or unescaped numbers or text.
IF (@value IS NULL) BEGIN;
SET @value=LEFT(@json,
PATINDEX('%[,}]%', REPLACE(@json, ']', '}')+'}')-1);
SET @json=SUBSTRING(@json, LEN(@value)+1, LEN(@json));
END;
--- Append @key and @value to @output:
SET @output=@output+@lf+@cr+
REPLICATE(@tab, @@NESTLEVEL-1)+
'<'+@key+'>'+
ISNULL(REPLACE(
REPLACE(@value, '\"', '"'), '\\', '\'), '')+
(CASE WHEN @nested=1
THEN @lf+@cr+REPLICATE(@tab, @@NESTLEVEL-1)
ELSE ''
END)+
'</'+@key+'>';
--- And again, error checks:
---
--- 1. If these are multiple values, the next character
--- should be a comma:
IF (@array=0 AND @json!='' AND LEFT(@json, 1)!=',')
RETURN @output+'Expected "," after value, found "'+
LEFT(@json, 1)+'"!';
--- 2. .. or, if this is an array, the next character
--- should be a comma or a closing angle bracket:
IF (@array=1 AND LEFT(@json, 1) NOT IN (',', ']'))
RETURN @output+'In array, expected "]" or "," after '+
'value, found "'+LEFT(@json, 1)+'"!';
--- If this is where the array is closed (i.e. if it's a
--- closing angle bracket)..
IF (@array=1 AND LEFT(@json, 1)=']') BEGIN;
SET @array=NULL;
SET @json=LTRIM(SUBSTRING(@json, 2, LEN(@json)));
--- After a closed array, there should be a comma:
IF (LEFT(@json, 1) NOT IN ('', ',')) BEGIN
RETURN 'Closed array, expected ","!';
END;
END;
SET @json=LTRIM(SUBSTRING(@json, 2, LEN(@json)+1));
IF (@array=0) SET @array=NULL;
END;
END;
--- Return the output:
RETURN CAST(@output AS xml);
END;
预期的结果是 XML 数据。到目前为止的实际结果是以下错误消息:
消息 9455,级别 16,状态 1,第 29 行 XML 解析:第 3 行,字符 3,非法限定名字符
解决方案
问题是 XML 标记名称不能以数字开头,但此 JSON 中的键是日期(“2019-07-23”等)。
为了解决这个问题,我们可以在以数字开头的键之前添加一个字符,方法是添加以下行:
IF @key LIKE '[0-9]%' SET @key='date'+@key
在这行之前:
--- Append @key and @value to @output:
这样,我们得到以下 XML:
<rates>
<date2019-07-23>
<USD>0.7605336601</USD>
</date2019-07-23>
<date2019-07-25>
<USD>0.7617709547</USD>
</date2019-07-25>
<date2019-07-26>
<USD>0.758719346</USD>
</date2019-07-26>
<date2019-07-24>
<USD>0.7616052506</USD>
</date2019-07-24>
<date2019-07-22>
<USD>0.7647459939</USD>
</date2019-07-22>
</rates>
<start_at>2019-07-20</start_at>
<base>CAD</base>
<end_at>2019-07-26</end_at>
后期编辑:
Shnugo 在评论中正确地指出,使用标签名称来携带信息是一个坏主意。最好在属性中移动该信息。为此,您可以使用以下代码替换函数的相关部分:
DECLARE @attribute VARCHAR(max)
IF @key LIKE '[0-9]%' SELECT @attribute=@key, @key='rate'
ELSE SET @attribute=NULL
--- Append @key and @value to @output:
SET @output=@output+@lf+@cr+
REPLICATE(@tab, @@NESTLEVEL-1)+
'<'+@key+ISNULL(' date="'+@attribute+'"','')+'>'+
ISNULL(REPLACE(
REPLACE(@value, '\"', '"'), '\\', '\'), '')+
(CASE WHEN @nested=1
THEN @lf+@cr+REPLICATE(@tab, @@NESTLEVEL-1)
ELSE ''
END)+
'</'+@key+'>';
这样,我们得到以下 XML:
<rates>
<date value="2019-07-23">
<USD>0.7605336601</USD>
</date>
<date value="2019-07-25">
<USD>0.7617709547</USD>
</date>
<date value="2019-07-26">
<USD>0.758719346</USD>
</date>
<date value="2019-07-24">
<USD>0.7616052506</USD>
</date>
<date value="2019-07-22">
<USD>0.7647459939</USD>
</date>
</rates>
<start_at>2019-07-20</start_at>
<base>CAD</base>
<end_at>2019-07-26</end_at>
如果我们走这条路线,我们可能还想将<USD>
标签更改为类似<currency code="USD">
.
推荐阅读
- r - 如何为长时间数据框创建列中时间和行中日期的矩阵?
- installation - 为 Chrome 扩展捆绑 Puppeteer 时引发错误
- esp32 - 保持 ESP32 AutoConnect Captive Portal 始终运行
- microsoft-graph-api - 使用 Graph API 在日历中创建事件会返回“ResourceNotFound”
- authentication - WSS 3.0 中嵌入的 ASP.net 4.0 框架应用程序
- amazon-web-services - 如何配置 Amazon S3 存储桶,以便外部供应商可以将每日文件放入该存储桶内的相关文件夹中?
- javascript - Three.JS - GLTF 模型加载缓慢。如何加快加载时间?
- reactjs - 反应钩子的顺序
- python - == 错误 == 解析 YAML 文件时发现异常
- python - 如何计算包含 3 个子列表的 2 个列表之间的曼哈顿距离