Working with JSON in SQL Server

Introduction

JSON (JavaScript Object Notation) is a lightweight data-interchange format. It is language-independent, easy to understand, and self-describing. It is used as an alternative to XML. JSON is a trendy data interchange format nowadays. Most modern services return the data in JSON text. SQL Server JSON is one of the needs for data developers to return JSON in SQL Server. In this article, let's learn how to implement JSON objects in SQL Server.

The Built-in JSON support in the SQL server is different from the native JSON type. JSON will be represented as an NVARCHAR type for the following reasons.

  • Cross feature compatibility
    The data type NVARCHAR has supported all the SQL server components such as Hekaton, temporal, column store tables, etc. It works with almost all the features of SQL Server. If we think JSON works with the X feature of SQL Server, the simple answer is that if NVARCHAR works with the X feature, JSON will also work. 
  • Migration
    Before SQL Server, developers stored JSON in the database as text. They needed to change the database schema and migrate the data into a new feature if JSON type was introduced. 
  • Client-side support
    Currently, there is no standardized JSON object type on the client side, such as an XmlDom object. JSON is treated as Object in JavaScript.

The following in-built functions are introduced in SQL Server to support JSON.

  • ISJSON
  • JSON_VALUE
  • JSON_QUERY
  • JSON_MODIFY
  • OPENJSON
  • FOR JSON

ISJSON (json string)

This function is very useful for checking the input string in JSON format before it stores in the database. It checks whether the supplied NVARCHAR text input is in the proper format according to JSON specification. This function returns an INT value; if the string is correctly formatted as JSON, it returns 1. Else it returns 0.

Example

To demonstrate the example, I have taken the following JSON string.

DECLARE @JSONData AS NVARCHAR(4000)  
SET @JSONData = N'{  
    "EmployeeInfo":{  
        "FirstName":"Jignesh",  
        "LastName":"Trivedi",  
        "Code":"CCEEDD",  
        "Addresses":[  
            { "Address":"Test 0", "City":"Gandhinagar", "State":"Gujarat"},  
            { "Address":"Test 1", "City":"Gandhinagar", "State":"Gujarat"}  
        ]  
    }  
}' 
SELECT  ISJSON(@JSONData) 

Output

ISJSON in SQL Server 2016

JSON_VALUE (json string, path)

It returns a scalar value from a JSON string. It parses JSON string and extracts scalar value from JSON string by a specific path. There is some specific format for providing the path. For example

  • '$' - reference entire JSON object
  • '$.Property1' - reference property1 in JSON object
  • '$[2]' - reference 2nd element in JSON array
  • '$.Property1.property2[4].property3' - reference nested property in JSON object

Example

I used the exact JSON string as the previous example to demonstrate the example.

SELECT JSON_VALUE(@JSONData,'$.EmployeeInfo.FirstName')  

JSON_VALUE in SQL Server 2016

SELECT JSON_VALUE(@JSONData,'$.EmployeeInfo.Addresses[0].Address')  

JSON_VALUE in SQL Server 2016

It returns null if the specified path is not found in the JSON object. If we want to throw the error if the specified path is not found in the JSON object, we can use the 'strict' keyword before the path.

JSON_VALUE in SQL Server 2016

SELECT JSON_VALUE(@JSONData,'strict $.EmployeeInfo.Addresses[0].Address1')  

JSON_VALUE in SQL Server 2016

JSON_QUERY(json string, path)

It extracts an array of data or objects from the JSON string. In the following example, I have extracted "Addresses" data from the JSON object and the first element of "Addresses" data from the JSON object.

SELECT JSON_QUERY(@JSONData,'$.EmployeeInfo.Addresses')  
SELECT JSON_QUERY(@JSONData,'$.EmployeeInfo.Addresses[1]')  

JSON_QUERY in SQL Server 2016

If the JSON string contains the duplicate property, i.e., two keys with the same name and on the same level, JSON_VALUE and JSON_QUERY functions return the first value that matches the path.

DECLARE @JSONData AS NVARCHAR(4000)  
SET @JSONData = N'{  
    "EmployeeInfo":{  
        "FirstName":"Jignesh",  
        "LastName":"Trivedi",  
        "FirstName":"Tejas",  
        "Code":"CCEEDD  
    }  
}'  
  
SELECT JSON_VALUE(@JSONData,'$.EmployeeInfo.FirstName')  

JSON_VALUE in SQL Server 2016

JSON_MODIFY (json string, path, new value)

This function returns an updated JSON string in NVARCHAR type. It takes three parameters; the first parameter is the JSON string, the second parameter is a path on which the value needs to change, and the third parameter is the value that needs to update. Using this function, we can insert, update, delete, or append a value to the JSON string.

Updating the existing value

To update the value of the existing JSON, we need to provide the exact path with the new value. For example, we can update the value of the FirstName field of the JSON string using the following query.

SET @JSONData = JSON_MODIFY(@JSONData,'$.EmployeeInfo.FirstName', 'Rakesh')  

JSON_MODIFY in SQL Server

In the following example, I have updated the Address field of the first element of EmployeeInfo.Addresses.

SET @JSONData = JSON_MODIFY(@JSONData,'$.EmployeeInfo.Addresses[0].Address', 'Test Address')  

Inserting a Value

This function inserts the value in the JSON string if the attribute in the provided path does not exist. If the provided path is already present, it will update the existing value with the new one. The new attribute is always added at the end of the existing string.

In the following example, I have added MiddleName as a new attribute at the EmployeeInfo root.

SET @JSONData = JSON_MODIFY(@JSONData,'$.EmployeeInfo.MiddleName ', 'G')  

JSON_MODIFY in SQL Server 2016

Appending a Value

Using the "append" keyword, we can append an item to an existing array in JSON. In the following example, I have added a new Address object in EmployeeInfo.Addresses element.

SET @JSONData = JSON_MODIFY(@JSONData,'append $.EmployeeInfo.Addresses', JSON_QUERY('{"Address":"Test 2", "City":"Bhavnagar", "State":"Gujarat"}','$'))  

JSON_MODIFY in SQL Server 2016

Multiple updates

Using the function JSON_MODIFY, we can update only one property; if we want to update multiple properties, we need to use multiple JSON_MODIFY calls.

In the following example, I have modified two elements: "FirstName" and "LastName."

SET @JSONData = JSON_MODIFY(JSON_MODIFY(@JSONData,'$.EmployeeInfo.FirstName', 'Ramesh'),'$.EmployeeInfo.LastName','Oza')  

JSON_MODIFY in SQL Server 2016

Deleting existing value

To delete an existing value, we need to provide the full path of the element and set a new value to NULL.

In the following example, I have deleted the element "FirstName."

SET @JSONData = JSON_MODIFY(@JSONData,'$.EmployeeInfo.FirstName', NULL)  

JSON_MODIFY in SQL Server 2016

Renaming Key

Renaming the Key is not directly supported, but we can add value with the new key and delete the old key. In the following example, I have renamed the key from "FirstName" to "ForeName."

SET @JSONData = JSON_MODIFY(JSON_MODIFY(@JSONData,'$.EmployeeInfo.ForeName',   
    JSON_VALUE(@JSONData,'$.EmployeeInfo.FirstName')),'$.EmployeeInfo.FirstName', NULL) 

JSON_MODIFY in SQL Server 2016

FOR JSON

Function FOR JSON is very useful when exporting SQL table data in JSON format. It is very similar to FOR XML function. Here, column names or aliases are key names for JSON objects. There are two options FOR JSON.

  • AUTO - It will create nested JSON sub-array based on the table hierarchy used in the query.
  • PATH - It enables us to define the required JSON structure using the column name or aliases. If we put dot (.) separated names in the column aliases, JSON properties follow the same naming convention.

The FOR JSON AUTO is suitable for most scenarios, but FOR JSON PATH is very useful in specific scenarios where we must control how JSON data is generated or nested. The FOR JSON PATH gives us full control to specify the output format for JSON data.

Syntax

SELECT COL1, COL 2 FROM   
TABLE   
FOR JSON AUTO| PATH 

Example

To demonstrate the example, I created EmployeeInfo and Addresses tables. I have also inserted some data. The ratio between tables is as follows.

SQL Server 2016

GO  
CREATE TABLE [dbo].[Addresses](  
    [Id] [int] IDENTITY(1,1) NOT NULL,  
    [EmployeeId] [int] NULL,  
    [Address] [varchar](250) NULL,  
    [City] [varchar](50) NULL,  
    [State] [varchar](50) NULL,  
 CONSTRAINT [PK_Addresses] PRIMARY KEY CLUSTERED   
(  
    [Id] ASC  
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]  
) ON [PRIMARY]  
  
GO  
CREATE TABLE [dbo].[EmployeeInfo](  
    [Id] [int] IDENTITY(1,1) NOT NULL,  
    [Code] [varchar](50) NULL,  
    [FirstName] [varchar](50) NULL,  
    [LastName] [varchar](50) NULL,  
 CONSTRAINT [PK_EmployeeInfo] PRIMARY KEY CLUSTERED   
(  
    [Id] ASC  
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]  
) ON [PRIMARY]  
  
GO  
SET IDENTITY_INSERT [dbo].[Addresses] ON   
  
GO  
INSERT [dbo].[Addresses] ([Id], [EmployeeId], [Address], [City], [State]) VALUES (1, 1, N'Test 0', N'Gandhinagar', N'Gujarat')  
GO  
INSERT [dbo].[Addresses] ([Id], [EmployeeId], [Address], [City], [State]) VALUES (2, 1, N'Test 1', N'Bhavnagar', N'Gujarat')  
GO  
INSERT [dbo].[Addresses] ([Id], [EmployeeId], [Address], [City], [State]) VALUES (3, 2, N'Test 2', N'Bhavnagar', N'Gujarat')  
GO  
INSERT [dbo].[Addresses] ([Id], [EmployeeId], [Address], [City], [State]) VALUES (4, 2, N'Test 3', N'Gandhinagar', N'Gujarat')  
GO  
SET IDENTITY_INSERT [dbo].[Addresses] OFF  
GO  
SET IDENTITY_INSERT [dbo].[EmployeeInfo] ON   
  
GO  
INSERT [dbo].[EmployeeInfo] ([Id], [Code], [FirstName], [LastName]) VALUES (1, N'ABCD', N'Jignesh', N'Trivedi')  
GO  
INSERT [dbo].[EmployeeInfo] ([Id], [Code], [FirstName], [LastName]) VALUES (2, N'XYZ', N'Rakesh', N'Trivedi')  
GO  
SET IDENTITY_INSERT [dbo].[EmployeeInfo] OFF  
GO  
ALTER TABLE [dbo].[Addresses]  WITH CHECK ADD  CONSTRAINT [FK_Addresses_EmployeeInfo] FOREIGN KEY([EmployeeId])  
REFERENCES [dbo].[EmployeeInfo] ([Id])  
GO  
ALTER TABLE [dbo].[Addresses] CHECK CONSTRAINT [FK_Addresses_EmployeeInfo]  
GO

Example - FOR JSON AUTO

SELECT * FROM [dbo].[EmployeeInfo] e  
INNER JOIN [dbo].[Addresses] Addresses ON e.Id = Addresses.EmployeeId  
WHERE e.Id = 1  
FOR JSON AUTO 

Output - JSON

[  
  {  
    "Id": 1,  
    "Code": "ABCD",  
    "FirstName": "Jignesh",  
    "LastName": "Trivedi",  
    "Addresses": [  
      {  
        "Id": 1,  
        "EmployeeId": 1,  
        "Address": "Test 0",  
        "City": "Gandhinagar",  
        "State": "Gujarat"  
      },  
      {  
        "Id": 2,  
        "EmployeeId": 1,  
        "Address": "Test 1",  
        "City": "Bhavnagar",  
        "State": "Gujarat"  
      }  
    ]  
  }  
]

Example - FOR JSON PATH

SELECT Id, Code, FirstName, LastName,  
    (SELECT Id, Address, City, State  
    FROM [dbo].[Addresses] a  
    WHERE a.EmployeeId = e.Id  
    FOR JSON AUTO  
    ) as Addresses  
FROM [dbo].[EmployeeInfo] e  
WHERE e.Id =1  
FOR JSON PATH, ROOT ('EmployeeInfo')  

Output

{  
  "EmployeeInfo": [  
    {  
      "Id": 1,  
      "Code": "ABCD",  
      "FirstName": "Jignesh",  
      "LastName": "Trivedi",  
      "Addresses": [  
        {  
          "Id": 1,  
          "Address": "Test 0",  
          "City": "Gandhinagar",  
          "State": "Gujarat"  
        },  
        {  
          "Id": 2,  
          "Address": "Test 1",  
          "City": "Bhavnagar",  
          "State": "Gujarat"  
        }  
      ]  
    }  
  ]  
}

OPENJSON

A table value function will generate a relational table with its contents from the JSON string. It will iterate through JSON object elements and arrays and generate a row for each element. We can generate a table without a pre-defined schema or a well-defined schema.

OPENJSON Without a Pre-defined Schema

This functionality will return the value as key-value pairs, including their type. The following example shows JSON data as key-value pair with its type.

DECLARE @JSONData AS NVARCHAR(4000)  
SET @JSONData = N'{  
        "FirstName":"Jignesh",  
        "LastName":"Trivedi",  
        "Code":"CCEEDD",  
        "Addresses":[  
            { "Address":"Test 0", "City":"Gandhinagar", "State":"Gujarat"},  
            { "Address":"Test 1", "City":"Gandhinagar", "State":"Gujarat"}  
        ]  
    }'  
  
  
SELECT * FROM OPENJSON(@JSONData)  

OPENJSON in SQL Server 2016

OPENJSON with a Pre-defined Schema

OPENJSON function can also generate a result set with a pre-defined schema. If we generate results with a pre-defined schema, it generates a table based on provided schema instead of key-value pair.

DECLARE @JSONData AS NVARCHAR(4000)  
SET @JSONData = N'{  
        "FirstName":"Jignesh",  
        "LastName":"Trivedi",  
        "Code":"CCEEDD"       
    }'  
  
SELECT * FROM OPENJSON(@JSONData)  
WITH (FirstName VARCHAR(50),  
LastName VARCHAR(50),  
Code VARCHAR(50))  

OPENJSON in SQL Server 2016

We can also access child JSON objects as well using the OPENJSON function. This can be done by CROSS APPLYing the JSON child element with the parent element.

In the following example, the EmployeeInfo and Addresses objects are fetched and applied to Cross join on. We need to use the "AS JSON" option in the column definition to specify which references the property that contains the child JSON node. In the column specified with the "AS JSON" option, the type must be NVARCHAR (MAX). Without this option, this function returns a NULL value instead of a child JSON object and returns a run time error in "strict" mode.

DECLARE @JSONData AS NVARCHAR(4000)  
SET @JSONData = N'{  
        "FirstName":"Jignesh",  
        "LastName":"Trivedi",  
        "Code":"CCEEDD",  
        "Addresses":[  
            { "Address":"Test 0", "City":"Bhavnagar", "State":"Gujarat"},  
            { "Address":"Test 1", "City":"Gandhinagar", "State":"Gujarat"}  
        ]  
    }'  
  
SELECT   
FirstName, LastName, Address, City, State   
FROM OPENJSON(@JSONData)  
WITH (FirstName VARCHAR(50),  
LastName VARCHAR(50),  
Code VARCHAR(50),  
Addresses NVARCHAR(max) as json  
) as B  
cross apply openjson (B.Addresses)  
with  
(  
    Address VARCHAR(50),  
    City VARCHAR(50),  
    State VARCHAR(50)  
)

OPENJSON in SQL Server 2016

Summary

All modern web applications support JSON, and it is one of the well-known data interchange formats. Now, SQL Server also supports the JSON format. There is no specific data type for JSON SQL Server like XML. We need to use NVARCHAR when we interact with JSON.

Many built-in functions are available with SQL Server, such as ISJSON, JSON_VALUE, JSON_QUERY, JSON_MODIFY, OPENJSON, and FOR JSON. Using these functions, we can play around with the JSON object.


Similar Articles