4

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'
)

2 Answers 2

3

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
Sign up to request clarification or add additional context in comments.

1 Comment

Very neat solution. I will accept but unfortunately for the project, we decided to simply rewrite the entire JSON everytime something need to be updated.
3

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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.