Need help with your JSON?
Try our JSON Formatter tool to automatically identify and fix syntax errors in your JSON. JSON Formatter tool
SQL Server JSON Functions and Formatting Options
Introduction: Why JSON in SQL Server?
Modern applications frequently exchange data using the JSON format. SQL Server, starting with SQL Server 2016, provides native functions to handle JSON data directly within the database engine. This allows you to:
- Format query results as JSON.
- Parse JSON strings stored in database columns.
- Query values and objects within JSON text.
- Modify values in JSON strings.
Leveraging these functions can simplify your application code, move data transformation logic closer to the data source, and improve performance in certain scenarios.
Generating JSON from SQL Queries (FOR JSON)
The FOR JSON
clause is appended to aSELECT
statement to format the results as JSON text. It comes with several modes: AUTO
,PATH
, and RAW
.
FOR JSON AUTO
AUTO
mode automatically structures the JSON output based on the tables and columns in the SELECT
statement. It's the simplest to use but offers less control over the structure compared to PATH
.
SELECT
CustomerID,
Name,
Email
FROM
Customers
FOR JSON AUTO;
Example Output (might vary slightly based on data):
[
{
"CustomerID": 1,
"Name": "Alice",
"Email": "alice@example.com"
},
{
"CustomerID": 2,
"Name": "Bob",
"Email": "bob@example.com"
}
]
With Joins, AUTO
mode will nest based on table joins:
SELECT
c.CustomerID,
c.Name,
o.OrderID,
o.OrderDate
FROM
Customers c
JOIN
Orders o ON c.CustomerID = o.CustomerID
ORDER BY
c.CustomerID
FOR JSON AUTO;
Example Output:
[
{
"CustomerID": 1,
"Name": "Alice",
"Orders": [
{
"OrderID": 101,
"OrderDate": "2023-01-15T00:00:00"
},
{
"OrderID": 105,
"OrderDate": "2023-02-20T00:00:00"
}
]
},
{
"CustomerID": 2,
"Name": "Bob",
"Orders": [
{
"OrderID": 102,
"OrderDate": "2023-01-16T00:00:00"
}
]
}
]
FOR JSON PATH
PATH
mode gives you full control over the JSON structure using column aliases formatted as paths (e.g., '$.path.to.property'
). This is more flexible for complex or specific JSON outputs.
SELECT
CustomerID AS "$.id",
Name AS "$.details.fullName",
Email AS "$.contact.email"
FROM
Customers
FOR JSON PATH;
Example Output:
[
{
"id": 1,
"details": {
"fullName": "Alice"
},
"contact": {
"email": "alice@example.com"
}
},
{
"id": 2,
"details": {
"fullName": "Bob"
},
"contact": {
"email": "bob@example.com"
}
}
]
Nesting with PATH
mode using subqueries or joins:
SELECT
c.CustomerID AS "id",
c.Name AS "name",
(
SELECT
o.OrderID AS "$.orderId",
o.OrderDate AS "$.orderDate"
FROM
Orders o
WHERE
o.CustomerID = c.CustomerID
FOR JSON PATH
) AS "orders" -- Alias names the JSON array property
FROM
Customers c
FOR JSON PATH;
Example Output:
[
{
"id": 1,
"name": "Alice",
"orders": [
{
"orderId": 101,
"orderDate": "2023-01-15T00:00:00"
},
{
"orderId": 105,
"orderDate": "2023-02-20T00:00:00"
}
]
},
{
"id": 2,
"name": "Bob",
"orders": [
{
"orderId": 102,
"orderDate": "2023-01-16T00:00:00"
}
]
}
]
FOR JSON RAW
RAW
mode generates a JSON array where each element is the value of a single column. It's typically used when you only select one column and want a simple array of values.
SELECT
Name
FROM
Customers
FOR JSON RAW;
Example Output:
[
"Alice",
"Bob"
]
FOR JSON Formatting Options
Various options can be combined with FOR JSON
to control the output format:
WITHOUT_ARRAY_WRAPPER
: Removes the default outer JSON array brackets[]
. Useful when you expect a single JSON object.INCLUDE_NULL_VALUES
: Includes properties withNULL
values. By default, they are omitted.ROOT('ElementName')
: Adds a single root key (or element name) to the JSON output, wrapping the result.AS JSON
: Casts the output to a JSON type, useful for chaining functions or storing JSON.
SELECT
CustomerID,
Name,
-- Assuming some customers might have NULL email
Email
FROM
Customers
WHERE CustomerID = 1 -- Select a single customer
FOR JSON AUTO, WITHOUT_ARRAY_WRAPPER, INCLUDE_NULL_VALUES, ROOT('CustomerData');
Example Output (if Email is NULL for CustomerID 1):
{
"CustomerData": {
"CustomerID": 1,
"Name": "Alice",
"Email": null
}
}
Parsing and Querying JSON (OPENJSON, JSON_VALUE, JSON_QUERY)
SQL Server provides functions to read and extract data from JSON strings.
OPENJSON
OPENJSON
is a table-valued function that parses JSON text and returns data in rows and columns. It's particularly useful for shredding JSON arrays or objects into a relational format.
Basic OPENJSON usage (default schema):
Without a WITH
clause, it returns key, value, and type for each element at the first level of the JSON structure.
DECLARE @jsonInfo NVARCHAR(MAX);
SET @jsonInfo = N'{
"name": "Product A",
"price": 19.99,
"tags": ["electronics", "gadget"],
"details": {
"weight": "1kg",
"color": "black"
}
}';
SELECT *
FROM OPENJSON(@jsonInfo);
Example Output:
key value type
--------- ------------------------------- ------
name Product A 1
price 19.99 2
tags ["electronics","gadget"] 4 -- Type 4 is array
details {"weight":"1kg","color":"black"} 5 -- Type 5 is object
(Types: 0=null, 1=string, 2=number, 3=boolean, 4=array, 5=object)
OPENJSON with explicit schema (WITH clause):
This is more common, defining the expected columns, their SQL data types, and the JSON path to the corresponding value.
DECLARE @jsonInfo NVARCHAR(MAX);
SET @jsonInfo = N'{
"name": "Product A",
"price": 19.99,
"inStock": true,
"releaseDate": "2023-01-01T00:00:00Z",
"details": { "weight": "1kg" }
}';
SELECT name, price, inStock, releaseDate, weight
FROM OPENJSON(@jsonInfo)
WITH (
name VARCHAR(100) '$.name',
price DECIMAL(10, 2) '$.price',
inStock BIT '$.inStock',
releaseDate DATETIME2 '$.releaseDate',
-- Extract from nested object
weight VARCHAR(50) '$.details.weight'
);
Example Output:
name price inStock releaseDate weight
----------- ------- ------- ------------------------- ------
Product A 19.99 1 2023-01-01 00:00:00.0000000 1kg
Extracting arrays with OPENJSON:
You can use OPENJSON
with CROSS APPLY
to shred an array property within a JSON column.
-- Assume a table 'Products' with a NVARCHAR(MAX) column 'TagsJson'
-- Example data: INSERT INTO Products (TagsJson) VALUES ('["electronics", "gadget", "popular"]');
-- Select Product ID and individual tags
SELECT
ProductID,
TagValue
FROM
Products
CROSS APPLY OPENJSON(TagsJson) WITH (TagValue VARCHAR(50) '$'); -- '$' means extract each element
Example Output:
ProductID TagValue
----------- ---------
1 electronics
1 gadget
1 popular
JSON_VALUE vs JSON_QUERY
These functions extract values from a JSON string without necessarily shredding the whole document into rows.
JSON_VALUE(jsonString, path)
: Extracts a scalar value (string, number, boolean) from the JSON string at the specified path. ReturnsNULL
if the path doesn't exist or points to an object/array.JSON_QUERY(jsonString, path)
: Extracts a JSON object or array from the JSON string at the specified path. ReturnsNULL
if the path doesn't exist or points to a scalar value.
DECLARE @jsonDoc NVARCHAR(MAX);
SET @jsonDoc = N'{
"id": 123,
"customer": {
"name": "Alice",
"city": "London"
},
"items": [
{"itemId": 1, "qty": 2},
{"itemId": 2, "qty": 1}
]
}';
SELECT
JSON_VALUE(@jsonDoc, '$.id') AS OrderId, -- Scalar
JSON_VALUE(@jsonDoc, '$.customer.name') AS CustomerName, -- Scalar
JSON_VALUE(@jsonDoc, '$.customer.city') AS CustomerCity, -- Scalar
JSON_VALUE(@jsonDoc, '$.items[0].itemId') AS FirstItemId, -- Scalar from array element
JSON_VALUE(@jsonDoc, '$.items') AS Items_JSON_VALUE_Result, -- Returns NULL (items is an array)
JSON_QUERY(@jsonDoc, '$.customer') AS CustomerObject, -- Object
JSON_QUERY(@jsonDoc, '$.items') AS ItemsArray, -- Array
JSON_QUERY(@jsonDoc, '$.customer.name') AS CustomerName_JSON_QUERY_Result; -- Returns NULL (name is scalar)
Example Output:
OrderId CustomerName CustomerCity FirstItemId Items_JSON_VALUE_Result CustomerObject ItemsArray CustomerName_JSON_QUERY_Result
--------- ------------ ------------ ----------- ------------------------- --------------------------------- -------------------------------------------- ------------------------------
123 Alice London 1 NULL {"name":"Alice","city":"London"} [{"itemId":1,"qty":2},{"itemId":2,"qty":1}] NULL
Use JSON_VALUE
for simple properties, JSON_QUERY
for objects or arrays.
Path Mode (Lax vs Strict):
JSON paths support lax
(default) and strict
modes.
lax
: ReturnsNULL
if a path step doesn't exist or is invalid.strict
: Raises an error if a path step doesn't exist or is invalid.
DECLARE @jsonDoc NVARCHAR(MAX) = N'{"name": "Test"}';
-- Lax mode (default) - returns NULL for non-existent path
SELECT JSON_VALUE(@jsonDoc, '$.address.city') AS LaxResult;
-- Strict mode - raises an error for non-existent path
-- SELECT JSON_VALUE(@jsonDoc, 'strict $.address.city') AS StrictResult;
Lax result:
LaxResult
---------
NULL
Strict mode is useful when you want to ensure the JSON conforms to an expected structure.
Modifying JSON Data (JSON_MODIFY)
The JSON_MODIFY(jsonString, path, newValue)
function updates a value at a specific path within a JSON string and returns the modified JSON string.
DECLARE @jsonDoc NVARCHAR(MAX);
SET @jsonDoc = N'{
"name": "Product A",
"price": 19.99,
"details": { "color": "black" }
}';
-- Update existing value
SET @jsonDoc = JSON_MODIFY(@jsonDoc, '$.price', 24.99);
-- Add new value (if path doesn't exist)
SET @jsonDoc = JSON_MODIFY(@jsonDoc, '$.inStock', 1);
-- Add value in nested object (creates object if it doesn't exist in lax mode)
SET @jsonDoc = JSON_MODIFY(@jsonDoc, '$.details.weight', '1.2kg');
-- Delete value (set newValue to NULL)
SET @jsonDoc = JSON_MODIFY(@jsonDoc, '$.details.color', NULL);
SELECT @jsonDoc AS ModifiedJson;
Example Output:
{
"name": "Product A",
"price": 24.99,
"details": {
"weight": "1.2kg"
},
"inStock": 1
}
JSON_MODIFY with Arrays:
You can modify array elements or append to arrays using JSON_MODIFY
.
DECLARE @jsonDoc NVARCHAR(MAX);
SET @jsonDoc = N'{
"tags": ["electronics", "gadget"]
}';
-- Append a value to the array
SET @jsonDoc = JSON_MODIFY(@jsonDoc, 'append $.tags', 'popular');
-- Update an element by index (0-based)
SET @jsonDoc = JSON_MODIFY(@jsonDoc, '$.tags[0]', 'hardware');
SELECT @jsonDoc AS ModifiedArrayJson;
Example Output:
{
"tags": [
"hardware",
"gadget",
"popular"
]
}
Note the use of 'append $.tags'
to add to the end of the array.
Validating JSON Data (ISJSON)
The ISJSON(jsonString)
function checks if a string contains valid JSON. It returns 1
if the string is valid JSON, 0
if it's invalid, and NULL
if the input string is NULL
.
SELECT
ISJSON('{"name": "valid"}') AS IsValid1,
ISJSON('[1, 2, 3]') AS IsValid2,
ISJSON('invalid json') AS IsValid3,
ISJSON(NULL) AS IsValidNull;
Example Output:
IsValid1 IsValid2 IsValid3 IsValidNull
----------- ----------- ----------- -----------
1 1 0 NULL
This is useful for validation constraints or before attempting to parse JSON columns.
Performance Considerations
Working with JSON data in SQL Server can sometimes have performance implications, especially with large JSON documents or frequent querying/modification.
- **Indexing JSON properties:** You can create indexes on specific properties within JSON columns to speed up queries using
JSON_VALUE
orJSON_QUERY
. This is typically done by creating computed columns based on the JSON properties and then indexing the computed columns.-- Add a computed column for a JSON property ALTER TABLE Products ADD ProductName AS JSON_VALUE(ProductDetailsJson, '$.name') -- Create an index on the computed column CREATE INDEX IX_Products_ProductName ON Products(ProductName);
- **Data Types:** Storing JSON in
NVARCHAR(MAX)
is standard. Ensure you use appropriate SQL types when extracting data withOPENJSON WITH
. - **Avoid Shredding Large JSON Repeatedly:** If you frequently need to access data within a JSON document, consider extracting commonly accessed properties into standard columns and indexing them, rather than repeatedly using
OPENJSON
orJSON_VALUE
on the raw JSON string. - **Hardware:** As with any database operation, sufficient CPU, memory, and fast storage are crucial for performance, especially when processing large amounts of JSON data.
Summary and Best Practices
- **Use
FOR JSON
** to easily format query results for web services or applications expecting JSON. UseAUTO
for simplicity,PATH
for precise structure control. - **Use
OPENJSON
** when you need to "shred" JSON data into a relational table format, often for querying, filtering, or joining with other relational data. - **Use
JSON_VALUE
andJSON_QUERY
** for extracting specific scalar values or JSON fragments without converting the whole document to rows. Remember the difference between scalar and object/array extraction. - **Use
JSON_MODIFY
** for targeted updates within a JSON string without needing to read and rewrite the entire content. - **Use
ISJSON
** to validate JSON data, especially before attempting parsing or modification. - Consider computed columns and indexing for performance-critical queries on JSON properties.
- Store JSON data in
NVARCHAR(MAX)
columns.
Putting It Together: An Example
Let's imagine a scenario where we have customer data with a JSON column storing preferences, and we want to list customers who prefer email notifications and format the output with their orders included as a nested array.
-- Assume table Customers with columns CustomerID INT, Name NVARCHAR(100), PreferencesJson NVARCHAR(MAX)
-- Assume table Orders with columns OrderID INT, CustomerID INT, OrderDate DATETIME2
-- Filter customers based on a JSON property and format output with nested orders
SELECT
c.CustomerID AS "id",
c.Name AS "name",
JSON_VALUE(c.PreferencesJson, '$.newsletter.email') AS "preferences.emailNewsletter", -- Extract scalar from JSON
(
SELECT
o.OrderID AS "$.orderId",
o.OrderDate AS "$.orderDate"
FROM
Orders o
WHERE
o.CustomerID = c.CustomerID
FOR JSON PATH
) AS "orders" -- Nest orders as an array
FROM
Customers c
WHERE
ISJSON(c.PreferencesJson) = 1 -- Ensure JSON is valid
AND JSON_VALUE(c.PreferencesJson, '$.newsletter.email') = 'true' -- Filter based on JSON property
FOR JSON PATH, ROOT('CustomersWithEmailPrefs'); -- Format as root object containing an array
This example combines ISJSON
for validation, JSON_VALUE
for filtering, and FOR JSON PATH
with a subquery for nested output, demonstrating how these functions can work together.
Conclusion
SQL Server's native JSON functions provide powerful capabilities for integrating JSON data into your relational database workflows. Whether you're generating JSON for APIs, parsing data from external sources, or querying/modifying JSON stored within your tables, these functions offer flexible and efficient ways to handle this popular data format directly within SQL Server. Understanding the different functions and formatting options allows developers to choose the most appropriate tools for their specific JSON-related tasks.
Need help with your JSON?
Try our JSON Formatter tool to automatically identify and fix syntax errors in your JSON. JSON Formatter tool