How do I keep FOR JSON PATH from escaping query results?

前端 未结 3 1303
小鲜肉
小鲜肉 2021-01-12 08:28

I\'m trying to write a fairly complicated SQL Query that produces JSON as the result. All is working great except for some hardcoded arrays I need to have deeper in the hier

3条回答
  •  天命终不由人
    2021-01-12 08:50

    There's some fascinating behavior going on in the optimizer for this query, and I'm not sure if it's a bug. The following query will not add escaping:

    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
    

    The CASE can be optimized away, and it is optimized away, and the end result is nicely nested JSON. But if we remove the ability to optimize things away, it degenerates into pasting in an escaped string:

    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
    

    It seems illogical that one query would result in processing typed JSON and the other would not, but there you go. JSON is not an actual type in T-SQL (unlike XML), so we can't CAST or CONVERT, but JSON_QUERY will do roughly the same thing:

    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
    

    Note that this also works if the argument is already JSON (in the constant case), so it's safe to add regardless.

提交回复
热议问题