Merge 'json_patch() function implementation' from Ihor Andrianov

First review #820
The function follows RFC 7386 JSON Merge Patch semantics:
* If the patch is null, the target is replaced with null
* If the patch contains a scalar value, the target is replaced with that
value
* If both target and patch are objects, the patch is recursively applied
* null values in the patch result in property removal from the target

Closes #821
This commit is contained in:
Pekka Enberg
2025-01-29 19:54:12 +02:00
6 changed files with 593 additions and 1 deletions

View File

@@ -8,6 +8,10 @@ do_execsql_test json5-ecma-script-1 {
} {{{"a":5,"b":6}}}
do_execsql_test json5-ecma-script-2 {
select json('{a:5,a:3}') ;
} {{{"a":5,"a":3}}}
do_execsql_test json5-ecma-script-3 {
SELECT json('{ MNO_123$xyz : 789 }');
} {{{"MNO_123$xyz":789}}}
@@ -576,3 +580,99 @@ do_execsql_test json_valid_3 {
do_execsql_test json_valid_9 {
SELECT json_valid(NULL);
} {}
do_execsql_test json-patch-basic-1 {
select json_patch('{"a":1}', '{"b":2}');
} {{{"a":1,"b":2}}}
do_execsql_test json-patch-basic-2 {
select json_patch('{"x":100,"y":200}', '{"z":300}');
} {{{"x":100,"y":200,"z":300}}}
do_execsql_test json-patch-preserve-duplicates-1 {
select json_patch('{"x":100,"x":200}', '{"z":300}');
} {{{"x":100,"x":200,"z":300}}}
do_execsql_test json-patch-preserve-duplicates-2 {
select json_patch('{"x":100,"x":200}', '{"x":900}');
} {{{"x":900,"x":200}}}
do_execsql_test json-patch-last-update-wins {
select json_patch('{"x":100,"c":200}', '{"x":900, "x":null}');
} {{{"c":200}}}
do_execsql_test json-patch-override-1 {
select json_patch('{"a":1,"b":2}', '{"b":3}');
} {{{"a":1,"b":3}}}
do_execsql_test json-patch-override-2 {
select json_patch('{"name":"john","age":25}', '{"age":26,"city":"NYC"}');
} {{{"name":"john","age":26,"city":"NYC"}}}
do_execsql_test json-patch-nested-1 {
select json_patch('{"user":{"name":"john"}}', '{"user":{"age":30}}');
} {{{"user":{"name":"john","age":30}}}}
do_execsql_test json-patch-nested-2 {
select json_patch('{"settings":{"theme":"dark"}}', '{"settings":{"theme":"light","font":"arial"}}');
} {{{"settings":{"theme":"light","font":"arial"}}}}
do_execsql_test json-patch-array-1 {
select json_patch('{"arr":[1,2,3]}', '{"arr":[4,5,6]}');
} {{{"arr":[4,5,6]}}}
do_execsql_test json-patch-array-2 {
select json_patch('{"list":["a","b"]}', '{"list":["c"]}');
} {{{"list":["c"]}}}
do_execsql_test json-patch-empty-1 {
select json_patch('{}', '{"a":1}');
} {{{"a":1}}}
do_execsql_test json-patch-empty-2 {
select json_patch('{"a":1}', '{}');
} {{{"a":1}}}
do_execsql_test json-patch-deep-nested-1 {
select json_patch(
'{"level1":{"level2":{"value":100}}}',
'{"level1":{"level2":{"newValue":200}}}'
);
} {{{"level1":{"level2":{"value":100,"newValue":200}}}}}
do_execsql_test json-patch-mixed-types-1 {
select json_patch(
'{"str":"hello","num":42,"bool":true}',
'{"arr":[1,2,3],"obj":{"x":1}}'
);
} {{{"str":"hello","num":42,"bool":true,"arr":[1,2,3],"obj":{"x":1}}}}
do_execsql_test json-patch-add-all-dup-keys-from-patch {
select json_patch(
'{"x":100,"x":200}',
'{"z":{}, "z":5, "z":100}'
);
} {{{"x":100,"x":200,"z":100}}}
do_execsql_test json-patch-first-occurance-patch {
select json_patch('{"x":100,"x":200}','{"x":{}, "x":5, "x":100}');
} {{{"x":100,"x":200}}}
do_execsql_test json-patch-complex-nested-dup-keys {
select json_patch(
'{"a":{"x":1,"x":2},"a":{"y":3},"b":[{"z":4,"z":5}]}',
'{"a":{"w":6},"b":[{"z":7,"z":8}],"b":{"z":9}}'
);
} {{{"a":{"x":1,"x":2,"w":6},"a":{"y":3},"b":{"z":9}}}}
do_execsql_test json-patch-unicode-dup-keys {
select json_patch(
'{"🔑":1,"🔑":2}',
'{"🗝️":3,"🗝️":4}'
);
} {{{"🔑":1,"🔑":2,"🗝️":4}}}
do_execsql_test json-patch-empty-string-dup-keys {
select json_patch(
'{"":1,"":2}',
'{"":3,"":4}'
);
} {{{"":4,"":2}}}
do_execsql_test json-patch-multiple-types-dup-keys {
select json_patch(
'{"x":100,"x":"str","x":true,"x":null}',
'{"y":1,"y":{},"y":[],"y":false}'
);
} {{{"x":100,"x":"str","x":true,"x":null,"y":false}}}
do_execsql_test json-patch-deep-nested-dup-keys {
select json_patch(
'{"a":{"b":{"c":1}},"a":{"b":{"c":2}},"a":{"b":{"d":3}}}',
'{"x":{"y":{"z":4}},"x":{"y":{"z":5}}}'
);
} {{{"a":{"b":{"c":1}},"a":{"b":{"c":2}},"a":{"b":{"d":3}},"x":{"y":{"z":5}}}}}
do_execsql_test json-patch-abomination {
select json_patch(
'{"a":{"b":{"x":1,"x":2,"y":{"z":3,"z":{"w":4}}},"b":[{"c":5,"c":6},{"d":{"e":7,"e":null}}],"f":{"g":[1,2,3],"g":{"h":8,"h":[4,5,6]}},"i":{"j":true,"j":{"k":false,"k":{"l":null,"l":"string"}}},"m":{"n":{"o":{"p":9,"p":{"q":10}},"o":{"r":11}}},"m":[{"s":{"t":12}},{"s":{"t":13,"t":{"u":14}}}]},"a":{"v":{"w":{"x":{"y":{"z":15}}}},"v":{"w":{"x":16,"x":{"y":17}}},"aa":[{"bb":{"cc":18,"cc":{"dd":19}}},{"bb":{"cc":{"dd":20},"cc":21}}]}}',
'{"a":{"b":{"x":{"new":"value"},"y":null},"b":{"c":{"updated":true},"d":{"e":{"replaced":100}}},"f":{"g":{"h":{"nested":"deep"}}},"i":{"j":{"k":{"l":{"modified":false}}}},"m":{"n":{"o":{"p":{"q":{"extra":"level"}}}},"s":null},"aa":[{"bb":{"cc":{"dd":{"ee":"new"}}}},{"bb":{"cc":{"dd":{"ff":"value"}}}}],"v":{"w":{"x":{"y":{"z":{"final":"update"}}}}}},"newTop":{"level":{"key":{"with":{"deep":{"nesting":true}}},"key":[{"array":{"in":{"deep":{"structure":null}}}}]}}}'
);
} {{{"a":{"b":{"x":{"new":"value"},"x":2,"c":{"updated":true},"d":{"e":{"replaced":100}}},"b":[{"c":5,"c":6},{"d":{"e":7,"e":null}}],"f":{"g":{"h":{"nested":"deep"}},"g":{"h":8,"h":[4,5,6]}},"i":{"j":{"k":{"l":{"modified":false}}},"j":{"k":false,"k":{"l":null,"l":"string"}}},"m":{"n":{"o":{"p":{"q":{"extra":"level"}},"p":{"q":10}},"o":{"r":11}}},"m":[{"s":{"t":12}},{"s":{"t":13,"t":{"u":14}}}],"aa":[{"bb":{"cc":{"dd":{"ee":"new"}}}},{"bb":{"cc":{"dd":{"ff":"value"}}}}],"v":{"w":{"x":{"y":{"z":{"final":"update"}}}}}},"a":{"v":{"w":{"x":{"y":{"z":15}}}},"v":{"w":{"x":16,"x":{"y":17}}},"aa":[{"bb":{"cc":18,"cc":{"dd":19}}},{"bb":{"cc":{"dd":20},"cc":21}}]},"newTop":{"level":{"key":[{"array":{"in":{"deep":{"structure":null}}}}]}}}}}