admin管理员组

文章数量:1410737

I have a working procedure that accepts a JSON string and parses it into a table as expected. What I need now is a way to reverse that; I need a procedure and/or function that can take the values inside a table and return a JSON in the same format. The structure of the table and JSON is defined, but none of the values in the table are known.

Simplified example:

DECLARE @MapTableInDB TABLE (
     Field  NVARCHAR(128)
    ,SType  NVARCHAR(128)
    ,NType  NVARCHAR(128)
    ,Source NVARCHAR(128));
DECLARE @JSON_MapFromApp NVARCHAR(MAX) = N'
{
     "FullName":           {"NType":"System.String",   "SType":"NVARCHAR(128)", "Source":"Name"}
    ,"EmployeeID":         {"NType":"System.String",   "SType":"NVARCHAR(128)", "Source":"Emplid"}
    ,"FirstName":          {"NType":"System.String",   "SType":"NVARCHAR(128)", "Source":"First Name"}
    ,"MiddleName":         {"NType":"System.String",   "SType":"NVARCHAR(128)", "Source":"Middle Name"}
    ,"LastName":           {"NType":"System.String",   "SType":"NVARCHAR(128)", "Source":"Last Name"}
}
';
INSERT INTO @MapTableInDB (Field,SType,NType,Source)
SELECT   j.[key]     AS Field
        ,m.SType     AS SType
        ,m.NType     AS NType
        ,m.Source    AS Source
FROM OPENJSON(@JSON_MapFromApp, '$') j
CROSS APPLY (SELECT * FROM OPENJSON(j.[value], '$') WITH (
     Source NVARCHAR(128) '$.Source'
    ,SType  NVARCHAR(128) '$.SType'
    ,NType  NVARCHAR(128) '$.NType'
)) AS m;

This works as expected and creates a table like:

Field           SType           NType           Source
--------------- --------------- --------------- ---------------
FullName        NVARCHAR(128)   System.String   Name
EmployeeID      NVARCHAR(128)   System.String   Emplid
FirstName       NVARCHAR(128)   System.String   First Name
MiddleName      NVARCHAR(128)   System.String   Middle Name
LastName        NVARCHAR(128)   System.String   Last Name

So far, my attempts to recreate the original JSON string are close, but need assistance:

SELECT   m.field
        ,(  SELECT m2.SType, m2.NType, m2.Source
            FROM @MapTableInDB m2
            WHERE (m2.Field = m.Field)
            FOR JSON PATH, WITHOUT_ARRAY_WRAPPER) AS '$'
FROM @MapTableInDB m
FOR JSON PATH, WITHOUT_ARRAY_WRAPPER;

Result:

/*
{"field":"FullName","$":"{\"SType\":\"NVARCHAR(128)\",\"NType\":\"System.String\",\"Source\":\"Name\"}"}
,{"field":"EmployeeID","$":"{\"SType\":\"NVARCHAR(128)\",\"NType\":\"System.String\",\"Source\":\"Emplid\"}"}
,{"field":"FirstName","$":"{\"SType\":\"NVARCHAR(128)\",\"NType\":\"System.String\",\"Source\":\"First Name\"}"}
,{"field":"MiddleName","$":"{\"SType\":\"NVARCHAR(128)\",\"NType\":\"System.String\",\"Source\":\"Middle Name\"}"}
,{"field":"LastName","$":"{\"SType\":\"NVARCHAR(128)\",\"NType\":\"System.String\",\"Source\":\"Last Name\"}"}
*/

My use case is a client app1 uploads the JSON formatted table structure via procedure1, then executes procedure2 to [re]build the table (working). Then client app2 fetches the JSON formatted table mapping (above issue) and then uploads content to the table created by procedure2.
The issue I need assistance with is making the return JSON be identical (semantically) to how it was submitted.

For context: I use it in client app2 as a definition for a nested hash table to transform a CSV into a datatable (works fine as long as it is formatted exactly the same as the submission). I do not want to store the raw JSON because I have other processes that add and remove columns outside the original app1 submission.

I have a working procedure that accepts a JSON string and parses it into a table as expected. What I need now is a way to reverse that; I need a procedure and/or function that can take the values inside a table and return a JSON in the same format. The structure of the table and JSON is defined, but none of the values in the table are known.

Simplified example:

DECLARE @MapTableInDB TABLE (
     Field  NVARCHAR(128)
    ,SType  NVARCHAR(128)
    ,NType  NVARCHAR(128)
    ,Source NVARCHAR(128));
DECLARE @JSON_MapFromApp NVARCHAR(MAX) = N'
{
     "FullName":           {"NType":"System.String",   "SType":"NVARCHAR(128)", "Source":"Name"}
    ,"EmployeeID":         {"NType":"System.String",   "SType":"NVARCHAR(128)", "Source":"Emplid"}
    ,"FirstName":          {"NType":"System.String",   "SType":"NVARCHAR(128)", "Source":"First Name"}
    ,"MiddleName":         {"NType":"System.String",   "SType":"NVARCHAR(128)", "Source":"Middle Name"}
    ,"LastName":           {"NType":"System.String",   "SType":"NVARCHAR(128)", "Source":"Last Name"}
}
';
INSERT INTO @MapTableInDB (Field,SType,NType,Source)
SELECT   j.[key]     AS Field
        ,m.SType     AS SType
        ,m.NType     AS NType
        ,m.Source    AS Source
FROM OPENJSON(@JSON_MapFromApp, '$') j
CROSS APPLY (SELECT * FROM OPENJSON(j.[value], '$') WITH (
     Source NVARCHAR(128) '$.Source'
    ,SType  NVARCHAR(128) '$.SType'
    ,NType  NVARCHAR(128) '$.NType'
)) AS m;

This works as expected and creates a table like:

Field           SType           NType           Source
--------------- --------------- --------------- ---------------
FullName        NVARCHAR(128)   System.String   Name
EmployeeID      NVARCHAR(128)   System.String   Emplid
FirstName       NVARCHAR(128)   System.String   First Name
MiddleName      NVARCHAR(128)   System.String   Middle Name
LastName        NVARCHAR(128)   System.String   Last Name

So far, my attempts to recreate the original JSON string are close, but need assistance:

SELECT   m.field
        ,(  SELECT m2.SType, m2.NType, m2.Source
            FROM @MapTableInDB m2
            WHERE (m2.Field = m.Field)
            FOR JSON PATH, WITHOUT_ARRAY_WRAPPER) AS '$'
FROM @MapTableInDB m
FOR JSON PATH, WITHOUT_ARRAY_WRAPPER;

Result:

/*
{"field":"FullName","$":"{\"SType\":\"NVARCHAR(128)\",\"NType\":\"System.String\",\"Source\":\"Name\"}"}
,{"field":"EmployeeID","$":"{\"SType\":\"NVARCHAR(128)\",\"NType\":\"System.String\",\"Source\":\"Emplid\"}"}
,{"field":"FirstName","$":"{\"SType\":\"NVARCHAR(128)\",\"NType\":\"System.String\",\"Source\":\"First Name\"}"}
,{"field":"MiddleName","$":"{\"SType\":\"NVARCHAR(128)\",\"NType\":\"System.String\",\"Source\":\"Middle Name\"}"}
,{"field":"LastName","$":"{\"SType\":\"NVARCHAR(128)\",\"NType\":\"System.String\",\"Source\":\"Last Name\"}"}
*/

My use case is a client app1 uploads the JSON formatted table structure via procedure1, then executes procedure2 to [re]build the table (working). Then client app2 fetches the JSON formatted table mapping (above issue) and then uploads content to the table created by procedure2.
The issue I need assistance with is making the return JSON be identical (semantically) to how it was submitted.

For context: I use it in client app2 as a definition for a nested hash table to transform a CSV into a datatable (works fine as long as it is formatted exactly the same as the submission). I do not want to store the raw JSON because I have other processes that add and remove columns outside the original app1 submission.

Share Improve this question edited Mar 4 at 16:57 Thom A 96.3k11 gold badges61 silver badges95 bronze badges asked Mar 4 at 15:42 Paul YoungPaul Young 497 bronze badges 3
  • "but need assistance:" With what, specifically? What part are you struggling to correct? – Thom A Commented Mar 4 at 15:46
  • I need assistance making the JSON extract to be semantically the same as the original. – Paul Young Commented Mar 4 at 15:57
  • 1 Something like: select '{' + STRING_AGG(t, ',') + '}' from ( select '"' + STRING_ESCAPE(field, 'json') + '" :' + (select Source, SType, NType FOR JSON PATH, without_array_wrapper) as t from @MapTableInDB ) x might work – siggemannen Commented Mar 4 at 16:07
Add a comment  | 

1 Answer 1

Reset to default 1

Unfortunately, SQL Server 2019 doesn't have the built in JSON tools to achieve this as you want. As such you'll actually need to use string aggregation to make your JSON instead. You can, however, still make the JSON object using FOR JSON PATH:

WITH CTE AS (
    SELECT MT.Field,
           (SELECT MT.SType, MT.NType, MT.Source FOR JSON PATH, WITHOUT_ARRAY_WRAPPER) AS FieldDetails
    FROM @MapTableInDB MT)
SELECT '{' + 
       STRING_AGG(CONCAT('"' + STRING_ESCAPE(C.Field,'JSON'),'":',C.FieldDetails),',')
       + '}'
FROM CTE C

This gives the following value:

{"FullName":{"SType":"NVARCHAR(128)","NType":"System.String","Source":"Name"},"EmployeeID":{"SType":"NVARCHAR(128)","NType":"System.String","Source":"Emplid"},"FirstName":{"SType":"NVARCHAR(128)","NType":"System.String","Source":"First Name"},"MiddleName":{"SType":"NVARCHAR(128)","NType":"System.String","Source":"Middle Name"},"LastName":{"SType":"NVARCHAR(128)","NType":"System.String","Source":"Last Name"}}

Which, when "prettified" as JSON, gives something like:

{
    "FullName": {
        "SType": "NVARCHAR(128)",
        "NType": "System.String",
        "Source": "Name"
    },
    "EmployeeID": {
        "SType": "NVARCHAR(128)",
        "NType": "System.String",
        "Source": "Emplid"
    },
    "FirstName": {
        "SType": "NVARCHAR(128)",
        "NType": "System.String",
        "Source": "First Name"
    },
    "MiddleName": {
        "SType": "NVARCHAR(128)",
        "NType": "System.String",
        "Source": "Middle Name"
    },
    "LastName": {
        "SType": "NVARCHAR(128)",
        "NType": "System.String",
        "Source": "Last Name"
    }
}

本文标签: How to convert Table to nested JSON in SQL Server 2019Stack Overflow