If you’re using OPENJSON()
, but you’re trying to remember how to select an inner fragment from the JSON document, read on.
The OPENJSON()
syntax allows you to convert JSON documents into a tabular view. It also allows you to select a nested JSON fragment from the JSON document.
The way to do this is with paths.
Paths
A path consists of the following:
- A dollar sign (
$
), which represents the context item. - A set of path steps. Path steps can contain the following elements and operators:
- Key names. For example,
$.pets
and$.pets.dogs
. If the key name starts with a dollar sign or contains special characters such as spaces, it must be surrounded with quotes (for example$."my pets"
). - Array elements. For example,
$.pets.dogs[1]
. Array indexes are zero-based, so this example selects the second element in the array. - The dot operator (
.
) indicates a member of an object. For example, in$.pets.dogs
,dogs
is a member ofpets
.
- Key names. For example,
Basic Example
Here’s a simple example to demonstrate.
DECLARE @json NVARCHAR(4000) = N'{
"pets" : {
"cats" : [
{ "id" : 1, "name" : "Fluffy", "sex" : "Female" },
{ "id" : 2, "name" : "Long Tail", "sex" : "Female" },
{ "id" : 3, "name" : "Scratch", "sex" : "Male" }
],
"dogs" : [
{ "id" : 1, "name" : "Fetch", "sex" : "Male" },
{ "id" : 2, "name" : "Fluffy", "sex" : "Male" },
{ "id" : 3, "name" : "Wag", "sex" : "Female" }
]
}
}';
SELECT *
FROM OPENJSON(@json, '$.pets.dogs')
WITH (
[id] int,
[name] varchar(60),
[sex] varchar(6)
);
Result:
+------+--------+--------+ | id | name | sex | |------+--------+--------| | 1 | Fetch | Male | | 2 | Fluffy | Male | | 3 | Wag | Female | +------+--------+--------+
In this case, the second argument to OPENJSON()
is '$.pets.dogs'
, which means that we’re selecting the value of the dogs
key, which itself is a child of pets
.
The dollar sign ($
) represents the context item.
Note that in this example, I also use the WITH
clause to define the schema. If I didn’t include that, the default schema would be used instead.
Here’s how it looks using the default schema.
DECLARE @json NVARCHAR(4000) = N'{
"pets" : {
"cats" : [
{ "id" : 1, "name" : "Fluffy", "sex" : "Female" },
{ "id" : 2, "name" : "Long Tail", "sex" : "Female" },
{ "id" : 3, "name" : "Scratch", "sex" : "Male" }
],
"dogs" : [
{ "id" : 1, "name" : "Fetch", "sex" : "Male" },
{ "id" : 2, "name" : "Fluffy", "sex" : "Male" },
{ "id" : 3, "name" : "Wag", "sex" : "Female" }
]
}
}';
SELECT *
FROM OPENJSON(@json, '$.pets.dogs');
Result:
+-------+-------------------------------------------------+--------+ | key | value | type | |-------+-------------------------------------------------+--------| | 0 | { "id" : 1, "name" : "Fetch", "sex" : "Male" } | 5 | | 1 | { "id" : 2, "name" : "Fluffy", "sex" : "Male" } | 5 | | 2 | { "id" : 3, "name" : "Wag", "sex" : "Female" } | 5 | +-------+-------------------------------------------------+--------+
So we’re still selecting the same nested JSON, it’s just that we’re using a different schema.
The default schema always returns three columns; key, value, and type.
Selecting Array Elements
As mentioned, you can use the square-bracket notation to select a specific element in an array.
Here’s an example.
DECLARE @json NVARCHAR(4000) = N'{
"pets" : {
"cats" : [
{ "id" : 1, "name" : "Fluffy", "sex" : "Female" },
{ "id" : 2, "name" : "Long Tail", "sex" : "Female" },
{ "id" : 3, "name" : "Scratch", "sex" : "Male" }
],
"dogs" : [
{ "id" : 1, "name" : "Fetch", "sex" : "Male" },
{ "id" : 2, "name" : "Fluffy", "sex" : "Male" },
{ "id" : 3, "name" : "Wag", "sex" : "Female" }
]
}
}';
SELECT *
FROM OPENJSON(@json, '$.pets.dogs[0]')
WITH (
[id] int,
[name] varchar(60),
[sex] varchar(6)
);
Result:
+------+--------+-------+ | id | name | sex | |------+--------+-------| | 1 | Fetch | Male | +------+--------+-------+
Seeing as array indexes are zero-based, specifying a value of 0
returns the first element in the array.
Here’s what this example looks like when using the default schema (i.e. without the WITH
clause).
DECLARE @json NVARCHAR(4000) = N'{
"pets" : {
"cats" : [
{ "id" : 1, "name" : "Fluffy", "sex" : "Female" },
{ "id" : 2, "name" : "Long Tail", "sex" : "Female" },
{ "id" : 3, "name" : "Scratch", "sex" : "Male" }
],
"dogs" : [
{ "id" : 1, "name" : "Fetch", "sex" : "Male" },
{ "id" : 2, "name" : "Fluffy", "sex" : "Male" },
{ "id" : 3, "name" : "Wag", "sex" : "Female" }
]
}
}';
SELECT *
FROM OPENJSON(@json, '$.pets.dogs[0]');
Result:
+-------+---------+--------+ | key | value | type | |-------+---------+--------| | id | 1 | 2 | | name | Fetch | 1 | | sex | Male | 1 | +-------+---------+--------+
Path Mode
When using paths, you have the option of declaring the path mode.
Path mode determines what happens when a path expression contains an error.
Path mode can be either lax
or strict
.
- In
lax
mode, the function returns empty values if the path can’t be found. For example, if you request the value$.pets.cows
, but the JSON doesn’t contain that key, the function returns null, but does not raise an error. - In
strict
mode, the function raises an error if the path can’t be found.
The default path mode is lax
, so if you don’t declare it, lax
is used.
Example
Here’s an example to demonstrate how each path mode handles missing paths.
Lax Mode
DECLARE @json NVARCHAR(4000) = N'{
"pets" : {
"cats" : [
{ "id" : 1, "name" : "Fluffy", "sex" : "Female" },
{ "id" : 2, "name" : "Long Tail", "sex" : "Female" },
{ "id" : 3, "name" : "Scratch", "sex" : "Male" }
],
"dogs" : [
{ "id" : 1, "name" : "Fetch", "sex" : "Male" },
{ "id" : 2, "name" : "Fluffy", "sex" : "Male" },
{ "id" : 3, "name" : "Wag", "sex" : "Female" }
]
}
}';
SELECT *
FROM OPENJSON(@json, 'lax $.pets.cows');
Result:
(0 rows affected)
So lax mode didn’t throw any errors. It simply resulted in zero rows being affected.
If we specified our own schema, and we selected the correct sub-object, but we used a missing path to map to a column name, it would return NULL
in that column.
DECLARE @json NVARCHAR(4000) = N'{
"pets" : {
"cats" : [
{ "id" : 1, "name" : "Fluffy", "sex" : "Female" },
{ "id" : 2, "name" : "Long Tail", "sex" : "Female" },
{ "id" : 3, "name" : "Scratch", "sex" : "Male" }
],
"dogs" : [
{ "id" : 1, "name" : "Fetch", "sex" : "Male" },
{ "id" : 2, "name" : "Fluffy", "sex" : "Male" },
{ "id" : 3, "name" : "Wag", "sex" : "Female" }
]
}
}'
SELECT *
FROM OPENJSON(@json, 'lax $.pets.dogs')
WITH (
[id] int 'lax $.id',
[name] varchar(60) 'lax $.name',
[color] varchar(6) 'lax $.color'
);
Result:
+------+--------+---------+ | id | name | color | |------+--------+---------| | 1 | Fetch | NULL | | 2 | Fluffy | NULL | | 3 | Wag | NULL | +------+--------+---------+
Strict Mode
Here’s what happens when we use strict mode.
DECLARE @json NVARCHAR(4000) = N'{
"pets" : {
"cats" : [
{ "id" : 1, "name" : "Fluffy", "sex" : "Female" },
{ "id" : 2, "name" : "Long Tail", "sex" : "Female" },
{ "id" : 3, "name" : "Scratch", "sex" : "Male" }
],
"dogs" : [
{ "id" : 1, "name" : "Fetch", "sex" : "Male" },
{ "id" : 2, "name" : "Fluffy", "sex" : "Male" },
{ "id" : 3, "name" : "Wag", "sex" : "Female" }
]
}
}';
SELECT *
FROM OPENJSON(@json, 'strict $.pets.cows');
Result:
Msg 13608, Level 16, State 3, Line 16 Property cannot be found on the specified JSON path.
As expected, it resulted in an error.
The same error occurs when we select the correct JSON key, but map a column to a non-existent key.
DECLARE @json NVARCHAR(4000) = N'{
"pets" : {
"cats" : [
{ "id" : 1, "name" : "Fluffy", "sex" : "Female" },
{ "id" : 2, "name" : "Long Tail", "sex" : "Female" },
{ "id" : 3, "name" : "Scratch", "sex" : "Male" }
],
"dogs" : [
{ "id" : 1, "name" : "Fetch", "sex" : "Male" },
{ "id" : 2, "name" : "Fluffy", "sex" : "Male" },
{ "id" : 3, "name" : "Wag", "sex" : "Female" }
]
}
}';
SELECT *
FROM OPENJSON(@json, 'strict $.pets.dogs')
WITH (
[id] int 'strict $.id',
[name] varchar(60) 'strict $.name',
[color] varchar(6) 'strict $.color'
);
Result:
Msg 13608, Level 16, State 6, Line 16 Property cannot be found on the specified JSON path.
Duplicate Paths
If your JSON document contains duplicate paths at the same nesting level, OPENJSON()
can return them all.
This is in contrast to JSON_VALUE()
and JSON_QUERY()
, both of which return only the first value that matches the path.
Here’s an example of using OPENJSON()
to return duplicate paths.
DECLARE @json NVARCHAR(4000) = N'{
"dog": {
"names": {
"name": "Fetch",
"name": "Good Dog"
}
}
}';
SELECT * FROM OPENJSON(@json, '$.dog.names');
Result:
+-------+----------+--------+ | key | value | type | |-------+----------+--------| | name | Fetch | 1 | | name | Good Dog | 1 | +-------+----------+--------+
Nested Sub-Objects
When defining your own schema, you can use the AS JSON
option to return a whole sub-object as its own JSON document.
DECLARE @json NVARCHAR(4000) = N'{
"pets" : {
"cats" : [
{ "id" : 1, "name" : "Fluffy", "sex" : "Female" },
{ "id" : 2, "name" : "Long Tail", "sex" : "Female" },
{ "id" : 3, "name" : "Scratch", "sex" : "Male" }
],
"dogs" : [
{ "id" : 1, "name" : "Fetch", "sex" : "Male" },
{ "id" : 2, "name" : "Fluffy", "sex" : "Male" },
{ "id" : 3, "name" : "Wag", "sex" : "Female" }
]
}
}';
SELECT *
FROM OPENJSON(@json, '$.pets')
WITH (
[dogs] nvarchar(max) '$.dogs' AS JSON
);
Result:
+--------+ | dogs | |--------| | [ { "id" : 1, "name" : "Fetch", "sex" : "Male" }, { "id" : 2, "name" : "Fluffy", "sex" : "Male" }, { "id" : 3, "name" : "Wag", "sex" : "Female" } ] | +--------+
If we hadn’t used the AS JSON
option, we would have received an error or NULL, depending on whether we’d specified lax
or strict
mode.
Here it is in each mode when omitting the AS JSON
option.
Lax mode
DECLARE @json NVARCHAR(4000) = N'{
"pets" : {
"cats" : [
{ "id" : 1, "name" : "Fluffy", "sex" : "Female" },
{ "id" : 2, "name" : "Long Tail", "sex" : "Female" },
{ "id" : 3, "name" : "Scratch", "sex" : "Male" }
],
"dogs" : [
{ "id" : 1, "name" : "Fetch", "sex" : "Male" },
{ "id" : 2, "name" : "Fluffy", "sex" : "Male" },
{ "id" : 3, "name" : "Wag", "sex" : "Female" }
]
}
}';
SELECT *
FROM OPENJSON(@json, '$.pets')
WITH (
[dogs] nvarchar(max) 'lax $.dogs'
);
Result:
+--------+ | dogs | |--------| | NULL | +--------+
Strict mode
DECLARE @json NVARCHAR(4000) = N'{
"pets" : {
"cats" : [
{ "id" : 1, "name" : "Fluffy", "sex" : "Female" },
{ "id" : 2, "name" : "Long Tail", "sex" : "Female" },
{ "id" : 3, "name" : "Scratch", "sex" : "Male" }
],
"dogs" : [
{ "id" : 1, "name" : "Fetch", "sex" : "Male" },
{ "id" : 2, "name" : "Fluffy", "sex" : "Male" },
{ "id" : 3, "name" : "Wag", "sex" : "Female" }
]
}
}';
SELECT *
FROM OPENJSON(@json, '$.pets')
WITH (
[dogs] nvarchar(max) 'strict $.dogs'
);
Result:
Msg 13624, Level 16, State 1, Line 16 Object or array cannot be found in the specified JSON path.