jsonsql-serverjson-valueazure-sql-edge

How to build new JSON with JSON_VALUE in SQL Server? Can't set value type


I have JSON like this:

{
  "_key": {
    "id": "3b8d1ed7-c3ec-32ec-a083-2ddc17152e94",
    "rootId": "15c85327-9628-3685-b84a-375b546ba92a",
  },
  "employeeInfo": {
    "idNumber": "3",
    "gender": "Male",
    "age": 20,
    ....
  },
  "product": {
    "plan": "prod",
    "class": "1",
    "_type": "Product",
    ...
  }
}

And I want to receive new JSON with desired fields. I'm using next query:

SELECT 
    '{ "employeeInfo": {"age: ' + 
    JSON_VALUE(info, '$.employeeInfo.age') + ', "gender": ' + 
    JSON_VALUE(info, '$.employeeInfo.gender') + ' }' AS info
FROM 
    item.[Item] AS c
INNER JOIN 
    (SELECT "rootId", MAX("revisionNo") AS maxRevisionNo 
     FROM item."Item"
     WHERE "rootId" = 'E3B455EF-D48E-338C-B6D4-FFD8B41243F9' 
     GROUP BY "rootId") AS subquery ON c."rootId" = subquery."rootId";

And I get this response:

{ "employeeInfo": {"age: 38, "gender": Female }

But it seems JSON_VALUE doesn't return type, so Female is without quotes. I don't know what fields will be requested, so I can't add quotes myself. How can I execute a query to return values with their types. I.e. I expect next response:

{ "employeeInfo": {"age: 38, "gender": "Female" }

I'm using this SQL Server version:

Microsoft Azure SQL Edge Developer (RTM) - 15.0.2000.1552 (ARM64)

UPD: I'm already have solution for postgres:

SELECT jsonb_strip_nulls(json_build_object('employee_info', json_build_object('age', c."info"->'employeeInfo' -> 'age', 'gender', c."info"->'employeeInfo' -> 'gender'), 'product', c."info"->'product')::jsonb) as info ...

And also need to build a request for sql-server. I will build this request dynamically when receive field names.

UPD2: I've created one more question which contains additional requirements. I need "product.plan" field also and have possibility to add any other fields How to build JSON with selected fields from JSON columns in SQL server keeping fields type


Solution

  • A slightly better syntax for @zhorov's excellent answer.

    SELECT 
       info = (
          SELECT
            j.age,
            j.gender
          FROM OPENJSON(i.info, '$.employeeInfo')
            WITH (
              age sql_variant,
              gender varchar(10)
            ) j
          FOR JSON PATH, WITHOUT_ARRAY_WRAPPER, ROOT('employeeInfo') 
       )
    FROM (
        SELECT *,
          rn = ROW_NUMBER() OVER (PARTITION BY i.rootId ORDER BY i.revisionNo DESC)
        FROM item.Item AS i
        WHERE i.rootId = 'E3B455EF-D48E-338C-B6D4-FFD8B41243F9' 
    ) i
    WHERE i.rn = 1;
    

    If you want to do it completely dynamically then you need OPENJSON without a schema. Then rebuild it using a combination of STRING_AGG CONCAT and STRING_ESCAPE.

    SELECT
      info = (
        SELECT
          CONCAT(
            '{"employeeInfo":{',
            STRING_AGG(
              CONCAT(
                '"',
                STRING_ESCAPE(j.[key], 'json'),
                '":',
                IIF(j.type = 1, CONCAT('"', STRING_ESCAPE(j.value, 'json'), '"'), j.value)
              ),
              ','
            ),
            '}}'
          )
        FROM OPENJSON(i.info, '$.employeeInfo') j
        WHERE j.[key] IN ('age', 'gender')
      )
    FROM (
        SELECT *,
          rn = ROW_NUMBER() OVER (PARTITION BY i.rootId ORDER BY i.revisionNo DESC)
        FROM item.Item AS i
        WHERE i.rootId = 'E3B455EF-D48E-338C-B6D4-FFD8B41243F9' 
    ) i
    WHERE i.rn = 1;