Bill Software Engineer
Bill Software Engineer

Reputation: 7782

How to JSON_MODIFY on Array of Array?

My strucutre look like this

Declare @layout NVARCHAR(MAX) = N'
    {
        "Sections": [
            {
                "SectionName":"Section1",
                "SectionOrder":1,
                "Renders":[
                    {
                        "RenderName":"Render1",
                        "RenderOrder":1,
                        "Fields":[
                            {
                                "FieldName":"Field1",
                                "FieldData":"Data1"
                            },
                            {
                                "FieldName":"Field2",
                                "FieldData":"Data2"
                            }
                        ]
                    },
                    {
                        "RenderName":"Render2",
                        "RenderOrder":2,
                        "Fields":[
                            {
                                "FieldName":"Field1",
                                "FieldData":"Data1"
                            },
                            {
                                "FieldName":"Field2",
                                "FieldData":"Data2"
                            }
                        ]
                    } 
                ]
            },
            {
                "SectionName":"Section2",
                "SectionOrder":2,
                "Renders":[
                    {
                        "RenderName":"Render1",
                        "RenderOrder":1,
                        "Fields":[
                            {
                                "FieldName":"Field1",
                                "FieldData":"Data1"
                            }
                        ]
                    },
                    {
                        "RenderName":"Render2",
                        "RenderOrder":2,
                        "Fields":[
                            {
                                "FieldName":"Field1",
                                "FieldData":"Data1"
                            },
                            {
                                "FieldName":"Field2",
                                "FieldData":"Data2"
                            }
                        ]
                    } 
                ]
            }
        ]
    }
'

What I would like to do is to do this:

update FieldData = 'DataUpdated' 
where FieldName = 'Field2' 
and RenderName = 'Render'
and SectionName = 'Section1'

How would I do this using JSON_MODIFY?

I can GET the data using the following query:

SELECT SectionName, SectionOrder, RenderName, RenderOrder, FieldName, FieldData FROM (
    SELECT SectionName, SectionOrder, RenderName, RenderOrder, Fields FROM (
        select SectionName, SectionOrder, Renders
        from OPENJSON(@layout,'$.Sections') 
        WITH (
            SectionName nvarchar(MAX) '$.SectionName',  
            SectionOrder nvarchar(MAX) '$.SectionOrder', 
            Renders nvarchar(MAX) '$.Renders' as JSON
        )
    ) as Sections
    CROSS APPLY OPENJSON(Renders,'$')
    WITH (
        RenderName nvarchar(MAX) '$.RenderName',  
        RenderOrder nvarchar(MAX) '$.RenderOrder', 
        Fields nvarchar(MAX) '$.Fields' as JSON
    )
) as Renders
CROSS APPLY OPENJSON(Fields,'$')
WITH (
    FieldName nvarchar(MAX) '$.FieldName',  
    FieldData nvarchar(MAX) '$.FieldData'
)

Upvotes: 4

Views: 6555

Answers (2)

Jonathan
Jonathan

Reputation: 4918

You can use CROSS APPLY without the WITH to get the key position in the array.

Execute the following procedure to see the updated JSON: execute TesteJSON

CREATE PROCEDURE TesteJSON
AS
BEGIN

DECLARE @layout NVARCHAR(MAX) = N'
    {
        "Sections": [
            {
                "SectionName":"Section1",
                "SectionOrder":1,
                "Renders":[
                    {
                        "RenderName":"Render1",
                        "RenderOrder":1,
                        "Fields":[
                            {
                                "FieldName":"Field1",
                                "FieldData":"Data1"
                            },
                            {
                                "FieldName":"Field2",
                                "FieldData":"Data2"
                            }
                        ]
                    },
                    {
                        "RenderName":"Render2",
                        "RenderOrder":2,
                        "Fields":[
                            {
                                "FieldName":"Field1",
                                "FieldData":"Data1"
                            },
                            {
                                "FieldName":"Field2",
                                "FieldData":"Data2"
                            }
                        ]
                    } 
                ]
            },
            {
                "SectionName":"Section2",
                "SectionOrder":2,
                "Renders":[
                    {
                        "RenderName":"Render1",
                        "RenderOrder":1,
                        "Fields":[
                            {
                                "FieldName":"Field1",
                                "FieldData":"Data1"
                            }
                        ]
                    },
                    {
                        "RenderName":"Render2",
                        "RenderOrder":2,
                        "Fields":[
                            {
                                "FieldName":"Field1",
                                "FieldData":"Data1"
                            },
                            {
                                "FieldName":"Field2",
                                "FieldData":"Data2"
                            }
                        ]
                    } 
                ]
            }
        ]
    }
';

DECLARE @TestTbl TABLE ( jsonObj VARCHAR(MAX) );

INSERT INTO @TestTbl ( jsonObj ) VALUES ( @layout );

-- Test the current data by uncommenting
-- SELECT * FROM @TestTbl;

WITH cte AS (
    SELECT Sections.[key] as k1, Renders.[key] as k2, Fields.[key] as k3, jsonObj FROM @TestTbl as tt
    CROSS APPLY OPENJSON(jsonObj, '$.Sections') Sections
    CROSS APPLY OPENJSON(Sections.value, '$.Renders' ) AS Renders
    CROSS APPLY OPENJSON(Renders.value, '$.Fields' ) AS Fields
    WHERE
        JSON_VALUE(Sections.value, '$.SectionName') = 'Section1'
        AND JSON_VALUE(Renders.value, '$.RenderName') = 'Render1'
        AND JSON_VALUE(Fields.value, '$.FieldName') = 'Field2'
)
UPDATE cte SET jsonObj = JSON_MODIFY(jsonObj, '$.Sections[' + k1 + '].Renders[' + k2 + '].Fields[' + k3 + '].FieldData', 'DataUpdated')

SELECT * FROM @TestTbl;

END

Upvotes: 2

Ed Harper
Ed Harper

Reputation: 21505

This is not as straightforward as one might hope. I was surprised that there seems to be no simple way to query the full path of an item in a JSON structure.

JSON_MODIFY can only accept array indicies when targetting a member of an array, so most of the work here goes into generating the indexes for each nested array member. It appears that the [key] column can only be generated when using OPENJSON without a WITH clause, so I couldn't reuse your query.

Additionally, JSON_MODIFY will only accept a string literal for the JSON path, so the update has to be carried out using dynamic SQL.

(Please note that this solution assumes that you want to update a specific RenderName e.g. 'Render1' - the question is unclear on this point.)

DECLARE @path nvarchar(2048)

SELECT @path = FORMATMESSAGE('SET @layout = JSON_MODIFY(@layout, ''$.Sections[%s].Renders[%s].Fields[%s].FieldData'', @newValue)' ,sectionindex, renderindex, [key])
FROM
(
    SELECT sectionindex, sectionName, b.[key] as renderindex, b.[value] AS bvalue, JSON_VALUE([Value], '$.RenderName') AS renderName
    FROM
        (SELECT [key] AS sectionindex, [Value] AS avalue, JSON_VALUE([Value], '$.SectionName') AS sectionName
        FROM OPENJSON(@layout, '$.Sections') ) AS sections
        CROSS APPLY OPENJSON(sections.avalue, '$.Renders') AS b
    ) AS renders
    CROSS APPLY OPENJSON(renders.bvalue,'$.Fields'
) AS d
WHERE JSON_VALUE([Value], '$.FieldName') = 'Field2' 
AND RenderName = 'Render1'
AND SectionName = 'Section1'

-- execute the update; this has to happen in dynamic SQL because the JSON_MODIFY path has to be a literal value, and cannot be a variable
EXEC sp_executeSQL @path, N'@layout nvarchar(max) OUTPUT, @newValue nvarchar(max)', @layout = @layout OUTPUT, @newValue = 'DateUpdated'

--check the results
SELECT sectionindex, sectionName, renderindex, rendername, [key] AS fieldindex, JSON_VALUE([Value], '$.FieldName') AS FieldName, JSON_VALUE([Value], '$.FieldData') AS FieldName
FROM
(
    SELECT sectionindex, sectionName, b.[key] AS renderindex, b.[value] AS bvalue, JSON_VALUE([Value], '$.RenderName') AS renderName
    FROM
        (SELECT [key] as sectionindex, [Value] as avalue, JSON_VALUE([Value], '$.SectionName') AS sectionName
        FROM OPENJSON(@layout, '$.Sections') ) AS sections
        CROSS APPLY OPENJSON(sections.avalue, '$.Renders') AS b
    ) AS renders
    CROSS APPLY OPENJSON(renders.bvalue,'$.Fields'
) AS d

Upvotes: 3

Related Questions