Parse JSON values with arbitrary keys - sql

I have a column in a table that is a JSON string. Part of these strings have the following format:
{
...
"rules": {
"rule_1": {
"results": [],
"isTestMode": true
},
"rule_2": {
"results": [],
"isTestMode": true
},
"rule_3": {
"results": [
{
"required": true,
"amount": 99.31
}
],
"isTestMode": true
},
"rule_4": {
"results": [],
"isTestMode": false
},
...
}
...
}
Within this nested "rules" object, I want to return true if results[0]["required"] = true AND "isTestMode" = false for any of the rules. The catch is that "rule_1", "rule_2", ... "rule_x" can have arbitrary names that aren't known in advance.
Is it possible to write a query that will iterate over all keys in '"rules"' and check if any one of them matches this condition? Is there any other way to achieve this?
If the keys were known in advance then I could do something like this:
WHERE
(JSON_ARRAY_LENGTH(JSON_EXTRACT(json, '$.rules.rule_1.results')) = 1
AND JSON_EXTRACT_SCALAR(json, '$rules.rule_1.results[0].required') = 'true'
AND JSON_EXTRACT_SCALAR(json, '$rules.rule_1.isTestMode') = 'false')
OR (JSON_ARRAY_LENGTH(JSON_EXTRACT(json, '$.rules.rule_2.results')) = 1
AND JSON_EXTRACT_SCALAR(json, '$rules.rule_2.results[0].required') = 'true'
AND JSON_EXTRACT_SCALAR(json, '$rules.rule_2.isTestMode') = 'false')
OR ...

You can extract rules property and transform it to MAP(varchar, json) and process it:
WITH dataset AS (
SELECT * FROM (VALUES
(JSON '{
"rules": {
"rule_1": {
"results": [],
"isTestMode": true
},
"rule_2": {
"results": [],
"isTestMode": true
},
"rule_3": {
"results": [
{
"required": true,
"amount": 99.31
}
],
"isTestMode": true
},
"rule_4": {
"results": [],
"isTestMode": false
}
}
}')
) AS t (json_value))
select cardinality(
filter(
map_values(cast(json_extract(json_value, '$.rules') as MAP(varchar, json))), -- trasnform into MAP and get it's values
js -> cast(json_extract(js, '$.isTestMode') as BOOLEAN) -- check isTestMode
AND cast(json_extract(js, '$.results[0].required') as BOOLEAN) -- check required of first element of `results`
)) > 0
from dataset
Which will give true for provided data.

I was able to solve this with a regex. Not ideal and would still like to know if this can be done using the built in JSON functions.
WHERE REGEXP_LIKE(json, '.*{"results":\[{"required":true,"amount":\d+.\d+"}],"isTestMode":false}.*')

Related

jsonb_set not working as expected with nested collections, resulting in null value returned

I am trying to update a nested collection in my Postgres table using json_set, however, my approach is resulting in a collection of null values.
id
payload
row_version
fbfd3b9d-bb20-4c1b-985f-0979890472ec
{ "slots": [ { "bannerXCreatorSettings": { "additionalFields": [ { "label": "test-label", "enabled": true, "mandatory": false, "additionalFieldId": "label", "maxCharacterLimit": 22, "additionalFieldType": "TEXT", "colorHexOptionsList": [] }, { "label": "test-label-2", "enabled": true, "mandatory": false, "additionalFieldId": "label2", "maxCharacterLimit": 55, "additionalFieldType": "TEXT", "colorHexOptionsList": [] } ] } } ]}
1
I have the following json payload stored in my table:
{
"slots": [
{
"bannerXCreatorSettings": {
"additionalFields": [
{
"label": "test-label",
"enabled": true,
"mandatory": false,
"additionalFieldId": "label",
"maxCharacterLimit": 22,
"additionalFieldType": "TEXT",
"colorHexOptionsList": []
},
{
"label": "test-label-2",
"enabled": true,
"mandatory": false,
"additionalFieldId": "label2",
"maxCharacterLimit": 55,
"additionalFieldType": "TEXT",
"colorHexOptionsList": []
}
]
}
}
]
}
Within the additionalFields collections, I want to add selectOptions. Like this:
{
"slots": [
{
"bannerXCreatorSettings": {
"additionalFields": [
{
"label": "test-label",
"enabled": true,
"mandatory": false,
"additionalFieldId": "label",
"maxCharacterLimit": 22,
"additionalFieldType": "TEXT",
"colorHexOptionsList": [],
"selectOptions": [] <-- new field
},
{
"label": "test-label-2",
"enabled": true,
"mandatory": false,
"additionalFieldId": "label2",
"maxCharacterLimit": 55,
"additionalFieldType": "TEXT",
"colorHexOptionsList": [],
"selectOptions": [] <-- new field
}
]
}
}
]
}
I have written the following SQL to try update the JSON payload within my table:
select
id,
payload,
row_version,
jsonb_set(
payload,
'{slots}',
(select
jsonb_agg(
jsonb_set(
slot_elem,
'{bannerXCreatorSettings}',
jsonb_set(
slot_elem -> 'bannerXCreatorSettings',
'{additionalFields}',
(
select
jsonb_agg(
jsonb_set(
field_element,
'{selectOptions}',
jsonb_build_array()))
from jsonb_array_elements(
slot_elem -> '{bannerXCreatorSettings}' #>'{additionalFields}') WITH ORDINALITY a_t(field_element, idx_1))
)
)
)
FROM
jsonb_array_elements(
payload #> '{slots}') WITH ORDINALITY t(slot_elem, idx))) as payload_update
My understanding of SQL is limited, however, I feel as though this should work. Unfortunally the above query results in the following:
{
"slots": [
null,
null
]
}

Navigate to path returned from diff function in dataweave on original payload

I am using dataweave diff function to compare two json objects. Diff is functioning correctly and returning the items that have changed, but it isn't returning anything but the differences. It is returning the path of the item that has changed, but I am not finding a good way to navigate to a string based path. I am only showing one group in the example but there could be many groups by different unique names.
{
"email": "someone#somewhere.com",
"frequency": 1,
"group1": [],
"group2": [],
etc...
)
So here is an example of what I am trying to describe:
Payload 1
{
"email": "someone#somewhere.com",
"frequency": 1,
"group1": [
{
"name": "item1",
"enabled": true
},
{
"name": "item2",
"enabled": true
},
{
"name": "item3",
"enabled": true
}
]
}
Payload 2:
{
"email": "someone#somewhere.com",
"frequency": 1,
"group1": [
{
"name": "item1",
"enabled": true
},
{
"name": "item2",
"enabled": true
},
{
"name": "item3",
"enabled": false
}
]
}
In this case diff between payload 1 and payload 2 would return
{
"matches": false,
"diffs": [
{
"expected": "true",
"actual": "false",
"path": "(root).group1[2].enabled"
}
]
}
I need to reach back into the original payload and navigate to (root).order[2] to get the name key/value pair. What is the best way to do this?
So at the end of the day I need the payload to be:
[
{
name: "item3",
enabled: false,
level: 1
}
]
I appreciate any assistance you can offer. I am also doing a map of the payload level so I know which piece of data to update when I traverse the results. So in this case the level would be 1 based on the fact that is an array off the main object.
This is my current dataweave I just need the first element to be dynamic based on the path that the diff returns
%dw 2.0
output application/json
---
payload.diffs map (item, index) -> {
name: initialPayload.group1[2].name, <-needs to be dynamic based on path
value: (item.actual) replace "\"" with "",
level: sizeOf(item.path splitBy ".")-1
}
I was able to resolve the issue with this weave.
%dw 2.0
fun GetValue(group: String, level: Number, itemkey: String) =
GetKeyValue((((group) splitBy /\./ reduce (e, acc = vars.initialPayload) -> acc[e])[level]), itemkey)
fun GetKeyValue(obj: Object, itemKey: String) =
(entriesOf(obj) filter (item) -> item.key as String == itemKey).value[0]
output application/json skipNullOn="everywhere"
---
{
changes: payload.diffs map (item, index) -> {
name:
if (sizeOf(item.path splitBy ".") - 1 == 1)
((item.path splitBy ".")[1])
else
GetValue((((item.path splitBy ".")[1]) splitBy "[")[0], (((item.path splitBy ".")[1]) splitBy "[")[1] replace "]" with "", "name"),
value: item.actual replace "\"" with "",
level: (sizeOf(item.path splitBy ".") - 1),
}
}

How to update multiple occurrence a specific value of a object present in array of object within Postgres JSON Field

Here is my JSON field where has multiple users with the same name. I want to update all users whose name is Devang to Dev
JSON
{
"user": [
{
"user_name": "Devang",
"user_weight": 0.7676846955248864
},
{
"user_name": "Meet",
"user_weight": 0.07447325861051013
},
{
"user_name": "Devang",
"user_weight": 0.056163873153859706
}
],
"address": [
{
"address_name": "India"
}
]
}
After Update The JSON would be
{
"user": [
{
"user_name": "Dev",
"user_weight": 0.7676846955248864
},
{
"user_name": "Meet",
"user_weight": 0.07447325861051013
},
{
"user_name": "Dev",
"user_weight": 0.056163873153859706
}
],
"address": [
{
"address_name": "India"
}
]
}
Here I have tried this query but update only the first occurrence due to subquery.
with cte as (
select id, ('{user,'||index-1||',user_name}')::text[] as json_path
from user_table, jsonb_array_elements(json_field->'user')
with ordinality arr(vals,index) where arr.vals->>'user_name' ='Devang'
)
update user_table
set json_field = jsonb_set(json_field,cte.json_path,'"Dev"',false)
from cte where user_table.id=cte.id;
Please also look at this DEMO
Any answer will be appreciated
You may use string function REPLACE:
UPDATE user_table
SET json_field = REPLACE(json_field :: TEXT, '"user_name": "Devang"', '"user_name": "Dev"') :: JSONB;
https://dbfiddle.uk/?rdbms=postgres_10&fiddle=fa36275977f85a1233bcbec150ada266

Karate- How can I pass a param as a key?

I have the following case and need to use firstKeyStr as a key, my code now is seeing "#firstKeyStr" as the key not the one stored in it
Code
Given path '/api/v1/sites'
When method get
Then status 200
And match response.success == true
And match response.data == "#object"
* def keys = karate.keysOf(response.data)
* def firstKeyStr = keys[0]
And match response.data."#firstKeyStr" == "#object"
Json Response Body
{
"success": true,
"data": {
"5ef34d0ca5a3c56ae14d2a23": {
"devices": [
"5f03192010a47f3e5b714193"
],
"groups": [
"5f0d9f30ef89e22778a2d230"
],
"users": [],
"triggers": [],
"alerts": [
"5f0d92b967bac60b84d3989b"
],
"name": "test",
"country": "US",
]
}
}
I'm looking for a way to pass this dynamic key (5ef34d0ca5a3c56ae14d2a23) in this line (And match response.data."#firstKeyStr" == "#object")
Since you are new to stack overflow, please read the following to best get help:
https://stackoverflow.com/help/how-to-ask
https://stackoverflow.com/help/someone-answers
As Peter said, your JSON is not well formed and the order of keys is not guaranteed (unless you have just a single key). The following code should get you going.
Sample Code:
Feature: firstKey
Scenario: firstKey
* def resp =
"""
{
"success": true,
"data": {
"5ef34d0ca5a3c56ae14d2a23": {
"devices": [
"5f03192010a47f3e5b714193"
],
"groups": [
"5f0d9f30ef89e22778a2d230"
],
"users": [],
"triggers": [],
"alerts": [
"5f0d92b967bac60b84d3989b"
],
"name": "test",
"country": "US"
}
}
}
"""
And match resp.data == "#object"
* def keys = karate.keysOf(resp.data)
* def firstKeyStr = keys[0]
* print firstKeyStr
* print (resp.data[firstKeyStr])
And match (resp.data[firstKeyStr]) == "#object"
Use round brackets so that JS is used instead of JsonPath:
* def response = { data: { a: 1, b: 2 } }
* def keys = karate.keysOf(response.data)
* def first = keys[0]
* match (response.data[first]) == 1

How to access a particular set of keys in array of Json values which i get as response?

For a particular API , I get a response which is similar to the following .
[
{ "name":"Ford", "model":"Mustang" },
{ "name":"BMW", "model": "320" },
{ "name":"Fiat", "model": "500" }
]
I want to store the values for the key 'name' in a separate variable .
Please read the documentation on using JsonPath carefully: https://github.com/intuit/karate#get
Here is an example which works with your data:
* def response =
"""
[
{ "name":"Ford", "model":"Mustang" },
{ "name":"BMW", "model": "320" },
{ "name":"Fiat", "model": "500" }
]
"""
* def names = $[*].name
* match names == ['Ford', 'BMW', 'Fiat']