From b4e7d59f4fa9653fad5bd1c833c3472dfe1aaf71 Mon Sep 17 00:00:00 2001 From: drh <> Date: Wed, 6 Mar 2024 14:30:42 +0000 Subject: [PATCH 1/3] Add the json_pretty(J) function for pretty-printing of JSON. An optional 2nd argument is text used for indentation, with a default value being four spaces. FossilOrigin-Name: 39552bd36c06fe9ee346cb71e0659baceccde031b67c0974f2dd14eb11ebc055 --- manifest | 15 +++--- manifest.uuid | 2 +- src/json.c | 143 +++++++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 152 insertions(+), 8 deletions(-) diff --git a/manifest b/manifest index da94066647..682a374dee 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Correction\sto\sthe\sprevious\scheck-in. -D 2024-03-06T12:28:55.128 +C Add\sthe\sjson_pretty(J)\sfunction\sfor\spretty-printing\sof\sJSON.\s\sAn\soptional\n2nd\sargument\sis\stext\sused\sfor\sindentation,\swith\sa\sdefault\svalue\sbeing\sfour\nspaces. +D 2024-03-06T14:30:42.046 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -710,7 +710,7 @@ F src/hash.h 3340ab6e1d13e725571d7cee6d3e3135f0779a7d8e76a9ce0a85971fa3953c51 F src/hwtime.h f9c2dfb84dce7acf95ce6d289e46f5f9d3d1afd328e53da8f8e9008e3b3caae6 F src/in-operator.md 10cd8f4bcd225a32518407c2fb2484089112fd71 F src/insert.c 3f0a94082d978bbdd33c38fefea15346c6c6bffb70bc645a71dc0f1f87dd3276 -F src/json.c 9337dac9d9d4c77461db55fd3e06e4d006f4283cb1da2b3a9b1f8d750701583e +F src/json.c e2e40760d6689134c3e2ece38c6a496b34ff5e2661a8f238444a119af666fdce F src/legacy.c d7874bc885906868cd51e6c2156698f2754f02d9eee1bae2d687323c3ca8e5aa F src/loadext.c 7432c944ff197046d67a1207790a1b13eec4548c85a9457eb0896bb3641dfb36 F src/main.c 438b95162acfa17b7d218f586f5bde11d6ae82bcf030c9611fc537556870ad6b @@ -2176,8 +2176,11 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P 027e5336acc26f57f21df4980928731026c30cf88688fa0b66f13ffa0b5da3a0 -R ee785ecad1867a820bb9e665fd92d3e8 +P 483fa2969e1e10cd8e8d2f9e3027871c65b1360b6c23897efe3ce63a3a55ae13 +R cdcb4f4f681bccfe01ba6f525ed68e3e +T *branch * json-pretty +T *sym-json-pretty * +T -sym-trunk * U drh -Z 74daf43f110405d8b8bf503998b8174f +Z 040cd116c25b3e34e2b8acda05bc14c7 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index dba854ccd3..0340f39ad2 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -483fa2969e1e10cd8e8d2f9e3027871c65b1360b6c23897efe3ce63a3a55ae13 \ No newline at end of file +39552bd36c06fe9ee346cb71e0659baceccde031b67c0974f2dd14eb11ebc055 \ No newline at end of file diff --git a/src/json.c b/src/json.c index fe7f938883..44ae846461 100644 --- a/src/json.c +++ b/src/json.c @@ -563,7 +563,6 @@ static void jsonAppendRawNZ(JsonString *p, const char *zIn, u32 N){ } } - /* Append formatted text (not to exceed N bytes) to the JsonString. */ static void jsonPrintf(int N, JsonString *p, const char *zFormat, ...){ @@ -2336,6 +2335,112 @@ static u32 jsonTranslateBlobToText( return i+n+sz; } +/* Context for recursion of json_pretty() +*/ +typedef struct JsonPretty JsonPretty; +struct JsonPretty { + JsonParse *pParse; /* The BLOB being rendered */ + JsonString *pOut; /* Generate pretty output into this string */ + const char *zIndent; /* Use this text for indentation */ + u32 szIndent; /* Bytes in zIndent[] */ + u32 nIndent; /* Current level of indentation */ +}; + +/* Append indentation to the pretty JSON under construction */ +static void jsonPrettyIndent(JsonPretty *pPretty){ + u32 jj; + for(jj=0; jjnIndent; jj++){ + jsonAppendRaw(pPretty->pOut, pPretty->zIndent, pPretty->szIndent); + } +} + +/* +** Translate the binary JSONB representation of JSON beginning at +** pParse->aBlob[i] into a JSON text string. Append the JSON +** text onto the end of pOut. Return the index in pParse->aBlob[] +** of the first byte past the end of the element that is translated. +** +** This is a variant of jsonTranslateBlobToText() that "pretty-prints" +** the output. Extra whitespace is inserted to make the JSON easier +** for humans to read. +** +** If an error is detected in the BLOB input, the pOut->eErr flag +** might get set to JSTRING_MALFORMED. But not all BLOB input errors +** are detected. So a malformed JSONB input might either result +** in an error, or in incorrect JSON. +** +** The pOut->eErr JSTRING_OOM flag is set on a OOM. +*/ +static u32 jsonTranslateBlobToPrettyText( + JsonPretty *pPretty, /* Pretty-printing context */ + u32 i /* Start rendering at this index */ +){ + u32 sz, n, j, iEnd; + const JsonParse *pParse = pPretty->pParse; + JsonString *pOut = pPretty->pOut; + n = jsonbPayloadSize(pParse, i, &sz); + if( n==0 ){ + pOut->eErr |= JSTRING_MALFORMED; + return pParse->nBlob+1; + } + switch( pParse->aBlob[i] & 0x0f ){ + case JSONB_ARRAY: { + j = i+n; + iEnd = j+sz; + jsonAppendChar(pOut, '['); + if( jnIndent++; + while( pOut->eErr==0 ){ + jsonPrettyIndent(pPretty); + j = jsonTranslateBlobToPrettyText(pPretty, j); + if( j>=iEnd ) break; + jsonAppendRawNZ(pOut, ",\n", 2); + } + jsonAppendChar(pOut, '\n'); + pPretty->nIndent--; + jsonPrettyIndent(pPretty); + } + jsonAppendChar(pOut, ']'); + i = iEnd; + break; + } + case JSONB_OBJECT: { + j = i+n; + iEnd = j+sz; + jsonAppendChar(pOut, '{'); + if( jnIndent++; + while( pOut->eErr==0 ){ + jsonPrettyIndent(pPretty); + j = jsonTranslateBlobToText(pParse, j, pOut); + if( j>iEnd ){ + pOut->eErr |= JSTRING_MALFORMED; + break; + } + jsonAppendRawNZ(pOut, ": ", 2); + j = jsonTranslateBlobToPrettyText(pPretty, j); + if( j>=iEnd ) break; + jsonAppendRawNZ(pOut, ",\n", 2); + } + jsonAppendChar(pOut, '\n'); + pPretty->nIndent--; + jsonPrettyIndent(pPretty); + } + jsonAppendChar(pOut, '}'); + i = iEnd; + break; + } + default: { + i = jsonTranslateBlobToText(pParse, i, pOut); + break; + } + } + return i; +} + + /* Return true if the input pJson ** ** For performance reasons, this routine does not do a detailed check of the @@ -4255,6 +4360,40 @@ json_type_done: jsonParseFree(p); } +/* +** json_pretty(JSON) +** json_pretty(JSON, INDENT) +** +** Return text that is a pretty-printed rendering of the input JSON. +** If the argument is not valid JSON, return NULL. +** +** The INDENT argument is text that is used for indentation. If omitted, +** it defaults to four spaces (the same as PostgreSQL). +*/ +static void jsonPrettyFunc( + sqlite3_context *ctx, + int argc, + sqlite3_value **argv +){ + JsonString s; /* The output string */ + JsonPretty x; /* Pretty printing context */ + + memset(&x, 0, sizeof(x)); + x.pParse = jsonParseFuncArg(ctx, argv[0], 0); + if( x.pParse==0 ) return; + x.pOut = &s; + jsonStringInit(&s, ctx); + if( argc==1 || (x.zIndent = (const char*)sqlite3_value_text(argv[1]))==0 ){ + x.zIndent = " "; + x.szIndent = 4; + }else{ + x.szIndent = (u32)strlen(x.zIndent); + } + jsonTranslateBlobToPrettyText(&x, 0); + jsonReturnString(&s, 0, 0); + jsonParseFree(x.pParse); +} + /* ** json_valid(JSON) ** json_valid(JSON, FLAGS) @@ -5269,6 +5408,8 @@ void sqlite3RegisterJsonFunctions(void){ JFUNCTION(jsonb_object, -1,0,1, 1,1,0, jsonObjectFunc), JFUNCTION(json_patch, 2,1,1, 0,0,0, jsonPatchFunc), JFUNCTION(jsonb_patch, 2,1,0, 0,1,0, jsonPatchFunc), + JFUNCTION(json_pretty, 1,1,0, 0,0,0, jsonPrettyFunc), + JFUNCTION(json_pretty, 2,1,0, 0,0,0, jsonPrettyFunc), JFUNCTION(json_quote, 1,0,1, 1,0,0, jsonQuoteFunc), JFUNCTION(json_remove, -1,1,1, 0,0,0, jsonRemoveFunc), JFUNCTION(jsonb_remove, -1,1,0, 0,1,0, jsonRemoveFunc), From 31c3ac9049a3e6c418ad3a1d7ce97c980968b9b9 Mon Sep 17 00:00:00 2001 From: stephan Date: Wed, 6 Mar 2024 14:42:06 +0000 Subject: [PATCH 2/3] Add a couple of json_pretty() examples to /fiddle. FossilOrigin-Name: d5e1687b1d49cec5daa8793ea138fdf3bd29436c1e67071707ec0594bd1c66c6 --- ext/wasm/fiddle/fiddle.js | 8 +++++++- manifest | 17 +++++++---------- manifest.uuid | 2 +- 3 files changed, 15 insertions(+), 12 deletions(-) diff --git a/ext/wasm/fiddle/fiddle.js b/ext/wasm/fiddle/fiddle.js index f409d92112..d28589835c 100644 --- a/ext/wasm/fiddle/fiddle.js +++ b/ext/wasm/fiddle/fiddle.js @@ -761,7 +761,13 @@ " FROM m2 GROUP BY cy\n", " )\n", "SELECT group_concat(rtrim(t),x'0a') as Mandelbrot FROM a;\n", - ]} + ]}, + {name: "JSON pretty-print", + sql: "select json_pretty(json_object('ex',json('[52,3.14159]')))" + }, + {name: "JSON pretty-print (with tabs)", + sql: "select json_pretty(json_object('ex',json('[52,3.14159]')),char(0x09))" + } ]; const newOpt = function(lbl,val){ const o = document.createElement('option'); diff --git a/manifest b/manifest index 682a374dee..3636941e49 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Add\sthe\sjson_pretty(J)\sfunction\sfor\spretty-printing\sof\sJSON.\s\sAn\soptional\n2nd\sargument\sis\stext\sused\sfor\sindentation,\swith\sa\sdefault\svalue\sbeing\sfour\nspaces. -D 2024-03-06T14:30:42.046 +C Add\sa\scouple\sof\sjson_pretty()\sexamples\sto\s/fiddle. +D 2024-03-06T14:42:06.357 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -641,7 +641,7 @@ F ext/wasm/example_extra_init.c 2347cd69d19d839ef4e5e77b7855103a7fe3ef2af86f2e8c F ext/wasm/fiddle.make 3c2eace29255d6ddd219f5d8cc2728cb28b9fe717ea80b6062c2a6178947a16b F ext/wasm/fiddle/emscripten.css 3d253a6fdb8983a2ac983855bfbdd4b6fa1ff267c28d69513dd6ef1f289ada3f F ext/wasm/fiddle/fiddle-worker.js 850e66fce39b89d59e161d1abac43a181a4caa89ddeea162765d660277cd84ce -F ext/wasm/fiddle/fiddle.js 04a638e3ed8fc9ca7c05cbe73ac4196e4529ec564639a76fbcecaffdf62bd983 +F ext/wasm/fiddle/fiddle.js b444a5646a9aac9f3fc06c53d78af5e1912eb235d69a8e6010723e4eb0e9d4a1 F ext/wasm/fiddle/index.html 5daf54e8f3d7777cbb1ca4f93affe28858dbfff25841cb4ab81d694efed28ec2 F ext/wasm/index-dist.html e91d76e4581185238fd3d42ed86ec600f7023ed3e3a944c5c356f25304bf1263 F ext/wasm/index.html b31ce41c0da476d5ffcef23069b9d3415b419d65af5779096ebcfbcbade453a9 @@ -2176,11 +2176,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P 483fa2969e1e10cd8e8d2f9e3027871c65b1360b6c23897efe3ce63a3a55ae13 -R cdcb4f4f681bccfe01ba6f525ed68e3e -T *branch * json-pretty -T *sym-json-pretty * -T -sym-trunk * -U drh -Z 040cd116c25b3e34e2b8acda05bc14c7 +P 39552bd36c06fe9ee346cb71e0659baceccde031b67c0974f2dd14eb11ebc055 +R f12e2eeb9bae468bef7713e1648b6a8a +U stephan +Z 3bec70ffdc2a1f410c64a91b9d3ada78 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 0340f39ad2..ea3e1e8458 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -39552bd36c06fe9ee346cb71e0659baceccde031b67c0974f2dd14eb11ebc055 \ No newline at end of file +d5e1687b1d49cec5daa8793ea138fdf3bd29436c1e67071707ec0594bd1c66c6 \ No newline at end of file From 1b977e3ae83db9fdfe70e650ca3bc3d22703ead9 Mon Sep 17 00:00:00 2001 From: drh <> Date: Wed, 6 Mar 2024 20:38:52 +0000 Subject: [PATCH 3/3] Add test cases for json_pretty(). FossilOrigin-Name: 6448b90708eeedef03e82dcb10d2879e1bc859d422b450c5fc403ffbe0343bed --- manifest | 15 ++++++++------- manifest.uuid | 2 +- test/json106.test | 6 ++++++ test/json108.test | 45 +++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 60 insertions(+), 8 deletions(-) create mode 100644 test/json108.test diff --git a/manifest b/manifest index 3636941e49..91c87d0a12 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Add\sa\scouple\sof\sjson_pretty()\sexamples\sto\s/fiddle. -D 2024-03-06T14:42:06.357 +C Add\stest\scases\sfor\sjson_pretty(). +D 2024-03-06T20:38:52.875 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -1353,8 +1353,9 @@ F test/json102.test 557a46e16df1aa9bdbc4076a71a45814ea0e7503d6621d87d42a8c04cbc2 F test/json103.test 53df87f83a4e5fa0c0a56eb29ff6c94055c6eb919f33316d62161a8880112dbe F test/json104.test 1b844a70cddcfa2e4cd81a5db0657b2e61e7f00868310f24f56a9ba0114348c1 F test/json105.test 043838b56e68f3252a0dcf5be1689016f6f3f05056f8dcfcdc9d074f4d932988 -F test/json106.test 1d46a9294e2ced35c7f87cebbcb9626d01abab04f1969d7ded7b6f6a1d9be0f2 +F test/json106.test 4aed3afd16549045d198a8d9cea00deea96e1f2ecf55864dce96cac558b8abef F test/json107.test 59054e815c8f6b67d634d44ace421cf975828fb5651c4460aa66015c8e19d562 +F test/json108.test 0a5f1e2d4b35a1bc33052563d2a5ede03052e2099e58cb424547656c898e0f49 F test/json501.test b95e2d14988b682a5cadf079dd6162f0f85fb74cd59c6b1f1624110104a974eb F test/json502.test 84634d3dbb521d2814e43624025b760c6198456c8197bbec6c977c0236648f5b F test/jsonb01.test f4cdfb4cf5a0c940091b17675ed9583f45add0c938f07d65b0de0e19d3a9a101 @@ -2176,8 +2177,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P 39552bd36c06fe9ee346cb71e0659baceccde031b67c0974f2dd14eb11ebc055 -R f12e2eeb9bae468bef7713e1648b6a8a -U stephan -Z 3bec70ffdc2a1f410c64a91b9d3ada78 +P d5e1687b1d49cec5daa8793ea138fdf3bd29436c1e67071707ec0594bd1c66c6 +R b5d425c8e8367f72e383ae1d2d9e4ea0 +U drh +Z ce296be00f3b33d5cde8abdb22061e01 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index ea3e1e8458..342c0db21d 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -d5e1687b1d49cec5daa8793ea138fdf3bd29436c1e67071707ec0594bd1c66c6 \ No newline at end of file +6448b90708eeedef03e82dcb10d2879e1bc859d422b450c5fc403ffbe0343bed \ No newline at end of file diff --git a/test/json106.test b/test/json106.test index 23fa028431..06859a10b4 100644 --- a/test/json106.test +++ b/test/json106.test @@ -67,6 +67,12 @@ for {set ii 1} {$ii<=5000} {incr ii} { FROM t1, kv WHERE p->>key IS NOT val } 0 + do_execsql_test $ii.8 { + SELECT j0 FROM t1 WHERE json(j0)!=json(json_pretty(j0)); + } {} + do_execsql_test $ii.9 { + SELECT j5 FROM t1 WHERE json(j5)!=json(json_pretty(j5)); + } {} } diff --git a/test/json108.test b/test/json108.test new file mode 100644 index 0000000000..71f3814dce --- /dev/null +++ b/test/json108.test @@ -0,0 +1,45 @@ +# 2024-03-06 +# +# The author disclaims copyright to this source code. In place of +# a legal notice, here is a blessing: +# +# May you do good and not evil. +# May you find forgiveness for yourself and forgive others. +# May you share freely, never taking more than you give. +# +#*********************************************************************** +# Invariant tests for JSON built around the randomjson extension +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl +set testprefix json108 + +# These tests require virtual table "json_tree" to run. +ifcapable !vtab { finish_test ; return } + +load_static_extension db randomjson +db eval { + CREATE TEMP TABLE t1(j0,j5); + WITH RECURSIVE c(n) AS (VALUES(0) UNION ALL SELECT n+1 FROM c WHERE n<9) + INSERT INTO t1 SELECT random_json(n), random_json5(n) FROM c; +} + +do_execsql_test 1.1 { + SELECT count(*) FROM t1 WHERE json(j0)==json(json_pretty(j0,NULL)); +} 10 +do_execsql_test 1.2 { + SELECT count(*) FROM t1 WHERE json(j0)==json(json_pretty(j0,NULL)); +} 10 +do_execsql_test 1.3 { + SELECT count(*) FROM t1 WHERE json(j0)==json(json_pretty(j0,'')); +} 10 +do_execsql_test 1.4 { + SELECT count(*) FROM t1 WHERE json(j0)==json(json_pretty(j0,char(9))); +} 10 +do_execsql_test 1.5 { + SELECT count(*) FROM t1 WHERE json(j0)==json(json_pretty(j0,'/*hello*/')); +} 10 + + +finish_test