如何防止FOR JSON PATH转义查询结果?

时间:2017-08-28 20:51:38

标签: sql-server json

我试图写一个相当复杂的SQL查询,结果产生JSON。一切都很好,除了一些硬编码数组我需要在层次结构中更深入,我必须使用UNION ALL来创建。我在这里制作了一个显示我的问题的查询(不需要数据 - 我在Azure SQL数据库中运行它):

SELECT
    'Hi' AS Greeting,
    (
        SELECT
            CASE WHEN DatePart(second, GetDate()) % 2 = 1 THEN
                'qwerty'
            ELSE
                'asdf'
            END AS Stuff
        FOR JSON PATH
    ) AS StuffArray,
    (
        CASE WHEN DatePart(second, GetDate()) % 2 = 1 THEN
        (
            SELECT 'qwerty' AS Stuff
            FOR JSON PATH
        )
        ELSE
        (
            SELECT 'asdf' AS Stuff
            FOR JSON PATH
        )
        END
    ) AS QuotedStuffArray,
    (
        CASE WHEN DatePart(second, GetDate()) % 2 = 1 THEN
        (
            SELECT * FROM
            (
                SELECT 'qwerty' AS Stuff
                UNION ALL
                SELECT 'zxcvb' AS Stuff
            ) AS SubSelect
            FOR JSON PATH
        )
        ELSE
        (
            SELECT 'asdf' AS Stuff
            FOR JSON PATH
        )
        END
    ) AS WhyItMatters,
    (
        SELECT * FROM
        (
            SELECT 'qwerty' AS Stuff
            UNION ALL
            SELECT 'zxcvb' AS Stuff
        ) AS SubSelect
        FOR JSON PATH
    ) AS ButThisIsFine
FOR JSON PATH

这输出这个JSON:

[
    {
        "Greeting": "Hi",
        "StuffArray": [
            {
                "Stuff": "qwerty"
            }
        ],
        "QuotedStuffArray": "[{\"Stuff\":\"qwerty\"}]",
        "WhyItMatters": "[{\"Stuff\":\"qwerty\"},{\"Stuff\":\"zxcvb\"}]",
        "ButThisIsFine": [
            {
                "Stuff": "qwerty"
            },
            {
                "Stuff": "zxcvb"
            }
        ]
    }
]

在此查询中,您将在基础对象之外的层次结构中看到四个不同的对象:StuffArrayQuotedStuffArrayWhyItMattersButThisIsFineStuffArray对象正是我想要的所有对象的样子 - 纯JSON没有任何转义。但是,当我开始将SELECT语法放在我的CASE语句中时,我的结果开始被引用,如QuotedStuffArray对象所示。所以对于前两个对象,这很好。但我有一个问题,我有时需要对两个硬编码值进行条件UNION,这迫使我将SELECT放入CASE语句中,如WhyItMatters所示宾语。 ButThisIsFine对象生成的输出格式化,就像我想要格式化WhyItMatters对象一样,但它删除了我需要的条件UNION

如何在保留条件WhyItMatters语句的同时使用ButThisIsFine对象生成纯JSON而不使用转义引号生成纯JSON?

3 个答案:

答案 0 :(得分:6)

在这个查询的优化器中有一些令人着迷的行为,我不确定它是不是一个bug。以下查询将添加转义:

SELECT
    'Hi' AS Greeting,
    (
        CASE WHEN 1 = 1 THEN (
            SELECT * FROM (
                SELECT 'qwerty' AS [Stuff]
                UNION ALL
                SELECT 'zxcvb' AS [Stuff]
            ) _
            FOR JSON PATH
        ) ELSE (
            SELECT 'asdf' AS [Stuff]
            FOR JSON PATH
        )
        END
    ) AS WhyItMatters
FOR JSON PATH

可以优化CASE 优化,最终结果是嵌套良好的JSON。但是如果我们删除了优化事物的能力,它就会退化为以转义字符串粘贴:

SELECT
    'Hi' AS Greeting,
    (
        CASE WHEN RAND() = 1 THEN (
            SELECT * FROM (
                SELECT 'qwerty' AS [Stuff]
                UNION ALL
                SELECT 'zxcvb' AS [Stuff]
            ) _
            FOR JSON PATH
        ) ELSE (
            SELECT 'asdf' AS [Stuff]
            FOR JSON PATH
        )
        END
    ) AS WhyItMatters
FOR JSON PATH

似乎不合逻辑的是,一个查询会导致处理类型化的JSON而另一个不会,但是你去了。 JSON不是T-SQL中的实际类型(与XML不同),因此我们不能CASTCONVERT,但JSON_QUERY将大致相同同样的事情:

SELECT
    'Hi' AS Greeting,
    JSON_QUERY(
        CASE WHEN RAND() = 1 THEN (
            SELECT * FROM (
                SELECT 'qwerty' AS [Stuff]
                UNION ALL
                SELECT 'zxcvb' AS [Stuff]
            ) _
            FOR JSON PATH
        ) ELSE (
            SELECT 'asdf' AS [Stuff]
            FOR JSON PATH
        )
        END
    ) AS WhyItMatters
FOR JSON PATH

请注意,如果参数已经是JSON(在常量情况下),这也有效,因此无论如何都可以安全添加。

答案 1 :(得分:2)

我找到了一个可能的解决方案,但我真的不喜欢它。我发布了我所拥有的东西,希望有人有更好的解决方案。

WHERE的每个分支上使用UNION语句,对我的CASE语句的肯定或完全否定都可以防止" strigifying"我的结果。

例如,此查询:

SELECT
    'Hi' AS Greeting,
    (
        SELECT * FROM
        (
            SELECT 'asdf' AS Stuff WHERE DatePart(second, GetDate()) % 2 = 0
            UNION ALL
            SELECT 'qwerty' AS Stuff WHERE DatePart(second, GetDate()) % 2 = 1
            UNION ALL
            SELECT 'zxcvb' AS Stuff WHERE DatePart(second, GetDate()) % 2 = 1
        ) AS SubSelect
        FOR JSON PATH
    ) AS Try1
FOR JSON PATH

提供以下结果:

[
    {
        "Greeting": "Hi",
        "Try1": [
            {
                "Stuff": "qwerty"
            },
            {
                "Stuff": "zxcvb"
            }
        ]
    }
]

如果找不到更好的东西,我可以继续前进。但这似乎是控制这种情况的一种黑客方式。

答案 2 :(得分:2)

我测试了两种解决方案的性能:

首先 - 通过JSON_QUERY

declare @i int = 1;
SELECT
    JSON_QUERY(
        CASE when @i = 1 THEN
        (
            SELECT * FROM
            (
                select textCol AS Stuff from table1 where id % 2 = 0 
                UNION ALL
                SELECT textCol AS Stuff from table1 where id % 2 <> 0
            ) AS SubSelect
            FOR JSON PATH
        )
        ELSE
        (
            SELECT textCol AS Stuff from table1
            FOR JSON PATH
        )
        END
    ) AS WhyItMatters
FOR JSON path

给我平均执行时间91ms。

第二

declare @i int = 1;
SELECT
    (SELECT * FROM
        (
            select textCol AS Stuff from table1 where id % 2 = 0 and @i = 1
            UNION ALL
            SELECT textCol AS Stuff from table1 where id % 2 <> 0 and @i = 1
            union all
            SELECT textCol AS Stuff from table1 where @i <> 1
        ) AS SubSelect
        FOR JSON PATH
    ) AS WhyItMatters
FOR JSON path

给我平均执行时间45ms。

table1包含12727行。生成的JSON长度约为1500000个字符。