如何使用mssql递归解析JSON字符串?

时间:2019-02-27 08:39:15

标签: json sql-server

我从this answer那里得到了这个想法,并提出了进一步的问题。我定义了一个变量:

declare @json nvarchar(max)
set @json = 
N'{
    "Book":{
        "IssueDate":"02-15-2019"
        , "Detail":{
            "Type":"Any Type"
            , "Author":{
                "Name":"Annie"
                , "Sex":"Female"
            }
        }
        , "Chapter":[
            {
                "Section":"1.1"
                , "Title":"Hello world."
            }
            ,
            {
                "Section":"1.2"
                , "Title":"Be happy."
            }       
        ]
        , "Sponsor":["A","B","C"]
    }
}'

然后我执行查询:

select 
    x.[key] topKey
    , y.[key] 
    , y.[value] 
    , '{"'+ y.[key] + '":' + y.[value] +'}' jsonString 
from openjson(@json) x
cross apply openjson(x.[value]) y

我从表(即@json)中重置了变量jsonString,并重复执行上述查询。

以下是执行的结果:

It's the first time I executive the sql query, and above is the result.

set @json = N'{"Detail":{"Type":"Any Type" , "Author":{"Name":"Annie"                  , "Sex":"Female"}}}'

set @json = N'{"Author":{"Name":"Annie", "Sex":"Female"}}'

set @json = N'{"Chapter":[{"Section":"1.1", "Title":"Hello world."},{"Section":"1.2"                  , "Title":"Be happy."}]}'

set @json = N'{"Sponsor":["A","B","C"]}'

我一直试图将上面的结果存储到一个表中,并创建了下面的函数:

create function ParseJson(
    @parent nvarchar(max), @json nvarchar(max))
returns @tempTable table (topKey nvarchar(max), FieldName nvarchar(max), FieldValue nvarchar(max), IsType int)
as
begin
    ; with cte as (
        select 
            x.[key] topKey, 
            y.[key] FieldName, 
            y.[value] FieldValue
            , iif([dbo].GetTypeId(y.[Key]) is null or y.[Key] = 'Type' ,0 ,1) IsType
        from 
            openjson(@json) x
            cross apply openjson(x.[value]) y
    )
    insert 
        @tempTable
    select 
        x.* 
    from 
        cte x
    union all
    select 
        z.* 
    from 
        cte y
    cross apply ParseJson(default,'{"'+ y.FieldName + '":' + y.FieldValue+'}') z
    where y.IsType=1

    return

end

-- execute
select * from ParseJson(default, @json)

IsType字段是检查是否需要递归的条件。

[dbo].GetTypeId是用户定义的函数,用于检查FieldValue是否为最终值,尽管它可能看起来并不适合于此。

以下是函数GetTypeIdType表:

create function GetTypeId(
    @typeName nvarchar(255)
)
returns nvarchar(1000)
as
begin
    declare 
        @typeId nvarchar(1000)
    select 
        @typeId=id
    from 
        [Type]
    where
        [Type].[Name]=@typeName

    return @typeId
end
go

the table <code>Type</code>

这是错误消息:

  

JSON文本格式不正确。在位置13处发现意外字符'0'。

2 个答案:

答案 0 :(得分:2)

对于如何使用mssql递归解析JSON字符串,我相信我对您的链接问题的回答也已经回答了这一问题。

从您提到的关于 type 的线索并查看您的代码,我们可以推断出您的JSON数据是不平凡的,并且存在某种模式。但是,您根本没有描述引擎盖下的机制。

基于我刚才所做的假设,让我们进一步猜测。假设您有一个名为Type的{​​{1}}作为非终端值的类型,而类型为SomeType1作为一种终端值的类型,由于某些东西由其他类型的值组成,因此不太可能解析但直接的符号;而且您必须告诉我们:

1)当我们得到StringGetTypeId时,SomeType1如何区分它们,一个是终端,另一个是非终端?

2)String在CTE中的工作方式以及属性名称如何与cross apply相关,因为它们将以GetTypeId的形式传递?

如果您的JSON数据有一个外部模式,我个人认为代码只是弄乱了东西,尤其是与属性和类型有关的东西;如果没有,那就更糟了..您要的是无价之宝,在这种情况下,恕我直言,无能为力。

答案 1 :(得分:2)

看起来和听起来像您在尝试在表中获得良好的非冗余编码,但还不完全清楚。

如果是这样,这是我用来执行类似操作的查询。查看输出,看看这是否是您想要的。有两点。首先,很容易从isjson()函数确定终端节点,该终端节点将返回0的值(和null)。其次,构建任意Ids比让json构建自己的ID困难。第三,我输入了一个或两个空值以捕获所有法律条件,最后...我作弊了格式(列为nvarchar(4000)和nvarchar(max)。 ..因此最终选择进行了转换...但是我不想弄混查询)

declare @j nvarchar(max) =  
N'{
  "Book":{
      "IssueDate":"02-15-2019"
      , "Detail":{
          "Type":"Any Type"
          , "Author":{
              "Name":"Annie"
              , "Sex":"Female"
          }
      }
      , "Chapter":[
          {
              "Section":"1.1"
              , "Title":"Hello world."
          }
          ,
          {
              "Section":"1.2"
              , "Title":"Be happy."
          }       
      ]
      , "Sponsor":["A","B","C",null]
      , "Hooey":null
  }
}';

with nodes as 
(
  select 
    [key] ParentId, 
    [key]  Id, 
    [key] Node, 
    [value] Val, 
    [type] NodeType, 
    isnull(abs(isjson([value])-1),1) IsTerminal
  from
    openjson( @j ) j 
  union all
  select
    nodes. Id, 
    nodes. Id + '.' + j.[key],
    j.[key], 
    j.[value], 
    j.[type],
    isnull(abs(isjson( j.[value] )-1),1)
  from
    nodes
    outer apply
    openjson( nodes.Val ) j
  where
    isjson( nodes.Val ) = 1
)
select 
  nodes.ParentId,
  nodes. Id,
  nodes.Node,
  case when NodeType= 5 then '{}' when NodeType=4 then '[]' else Val end Val,
  nodes.NodeType,
  nodes.IsTerminal
from 
  nodes

...返回:

ParentId             Id                             Node       Val                  NodeType IsTerminal
-------------------- ------------------------------ ---------- -------------------- -------- -----------
Book                 Book                           Book       {}                   5        0
Book                 Book.IssueDate                 IssueDate  02-15-2019           1        1
Book                 Book.Detail                    Detail     {}                   5        0
Book                 Book.Chapter                   Chapter    []                   4        0
Book                 Book.Sponsor                   Sponsor    []                   4        0
Book                 Book.Hooey                     Hooey      NULL                 0        1
Book.Sponsor         Book.Sponsor.0                 0          A                    1        1
Book.Sponsor         Book.Sponsor.1                 1          B                    1        1
Book.Sponsor         Book.Sponsor.2                 2          C                    1        1
Book.Sponsor         Book.Sponsor.3                 3          NULL                 0        1
Book.Chapter         Book.Chapter.0                 0          {}                   5        0
Book.Chapter         Book.Chapter.1                 1          {}                   5        0
Book.Chapter.1       Book.Chapter.1.Section         Section    1.2                  1        1
Book.Chapter.1       Book.Chapter.1.Title           Title      Be happy.            1        1
Book.Chapter.0       Book.Chapter.0.Section         Section    1.1                  1        1
Book.Chapter.0       Book.Chapter.0.Title           Title      Hello world.         1        1
Book.Detail          Book.Detail.Type               Type       Any Type             1        1
Book.Detail          Book.Detail.Author             Author     {}                   5        0
Book.Detail.Author   Book.Detail.Author.Name        Name       Annie                1        1
Book.Detail.Author   Book.Detail.Author.Sex         Sex        Female               1        1

(20 row(s) affected)

这显示了ParentIdIdNode,但实际上,Id列是多余的。您不需要它来重建json。但是,可能方便的是包括一个序列。

最后一点...我认为在cte内部进行递归比在外部进行更容易,因为您不必交叉应用函数...如参考答案所示。如果愿意,您仍然可以将其封装在函数中。

编辑(可能还有TL; NR)

我建议对节点进行排序对于以后的重组是个好主意...并且将选择放入函数中可能是可取的...然后我对此感到内:-)所以,这里'蒂斯。

结果输出不是按整体文档顺序,而是按 assembly 顺序。也就是说,对于任何给定节点,保证所有上级节点在输出中都更早...并且父节点的所有子节点都按文档顺序排列。我在函数中添加了一个相对的序号和一个深度指示符,认为这些值在某些情况下可能有用。

在递归位于cte内部的情况下,使其成为一个函数的一件好事是,结果函数可以是内联表值函数,通常比累积表变量的表值函数效率更高。

create function dbo.JsonNodes( @j nvarchar( max ) ) returns table as return 
(        
  with nodes as 
  (
    select 
      [key] ParentId, 
      [key] Id, 
      [key] Node, 
      [value] Val, 
      [type] Type, 
      isnull( abs( isjson( [value] ) -1 ), 1 ) IsLeaf,
      1 Depth,
      convert( bigint, 1 ) Seq
    from
      openjson( @j ) j 
    union all
    select
      nodes.Id, 
      nodes.Id + '.' + j.[key],
      j.[key], 
      j.[value], 
      j.[type],
      isnull( abs( isjson( j.[value] ) -1 ), 1 ),
      nodes.Depth + 1,
      row_number() over( partition by nodes.Id order by nodes.Id )
    from
      nodes
      outer apply
      openjson( nodes.Val ) j
    where
      isjson( nodes.Val ) = 1
  )
  select
    ParentId,
    Id,
    Node,
    case when Type=5 then '{}' when Type=4 then '[]' else Val end Val,
    Type,
    IsLeaf,
    Depth,
    Seq
  from 
    nodes
)