From a45c9a44fcd1e7458c4e104c19f7c57e684c7458 Mon Sep 17 00:00:00 2001 From: Christoph Knittel Date: Fri, 8 Mar 2024 17:03:03 +0100 Subject: [PATCH] Use optional args for JSON.stringify and JSON.parseExn --- CHANGELOG.md | 1 + scripts/DocTests.mjs | 4 +- src/Core__JSON.res | 39 ++++++++++++------ src/Core__JSON.resi | 97 +++++++++++++++++++++++++++++++++++++++++--- test/TempTests.mjs | 2 +- test/Test.mjs | 2 +- 6 files changed, 122 insertions(+), 23 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 23039663..05313060 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## Next version +- Add optional arguments to `JSON.stringify` and `JSON.parseExn` and deprecate `JSON.stringifyWithIndent`, `JSON.stringifyWithReplacer`, `JSON.parseExnWithReviver` etc. https://github.com/rescript-association/rescript-core/pull/201 - BREAKING: Intl types: simplify bindings for constructors / functions with optional arguments. https://github.com/rescript-association/rescript-core/pull/198 - Fix: Expose Intl.Common. https://github.com/rescript-association/rescript-core/pull/197 - Add `Array.flatMapWithIndex` https://github.com/rescript-association/rescript-core/pull/199 diff --git a/scripts/DocTests.mjs b/scripts/DocTests.mjs index d07cd4bd..77aaedf0 100644 --- a/scripts/DocTests.mjs +++ b/scripts/DocTests.mjs @@ -65,7 +65,7 @@ function prepareCompiler() { stdio: "ignore", cwd: compilerDir }); - var dict = JSON.parse(Fs.readFileSync(Path.join(corePath, "package.json"))); + var dict = JSON.parse(Fs.readFileSync(Path.join(corePath, "package.json")), undefined); var currentCoreVersion; if (!Array.isArray(dict) && (dict === null || typeof dict !== "object") && typeof dict !== "number" && typeof dict !== "string" && typeof dict !== "boolean") { throw { @@ -179,7 +179,7 @@ function extractDocFromFile(file) { "doc", file ]); - return Tools_Docgen.decodeFromJson(JSON.parse(spawn.stdout.toString())); + return Tools_Docgen.decodeFromJson(JSON.parse(spawn.stdout.toString(), undefined)); } function getExamples(param) { diff --git a/src/Core__JSON.res b/src/Core__JSON.res index 11f65202..1701c780 100644 --- a/src/Core__JSON.res +++ b/src/Core__JSON.res @@ -7,25 +7,38 @@ type rec t = Js.Json.t = | Object(Core__Dict.t) | Array(array) -@raises @val external parseExn: string => t = "JSON.parse" -@raises @val external parseExnWithReviver: (string, (string, t) => t) => t = "JSON.parse" -@val external stringify: t => string = "JSON.stringify" -@val external stringifyWithIndent: (t, @as(json`null`) _, int) => string = "JSON.stringify" -@val external stringifyWithReplacer: (t, (string, t) => t) => string = "JSON.stringify" -@val +@unboxed +type replacer = Keys(array) | Replacer((string, t) => t) + +@raises @val external parseExn: (string, ~reviver: (string, t) => t=?) => t = "JSON.parse" +@deprecated("Use `parseExn` with optional parameter instead") @raises @val +external parseExnWithReviver: (string, (string, t) => t) => t = "JSON.parse" + +@val external stringify: (t, ~replacer: replacer=?, ~space: int=?) => string = "JSON.stringify" +@deprecated("Use `stringify` with optional parameter instead") @val +external stringifyWithIndent: (t, @as(json`null`) _, int) => string = "JSON.stringify" +@deprecated("Use `stringify` with optional parameter instead") @val +external stringifyWithReplacer: (t, (string, t) => t) => string = "JSON.stringify" +@deprecated("Use `stringify` with optional parameters instead") @val external stringifyWithReplacerAndIndent: (t, (string, t) => t, int) => string = "JSON.stringify" -@val external stringifyWithFilter: (t, array) => string = "JSON.stringify" -@val external stringifyWithFilterAndIndent: (t, array, int) => string = "JSON.stringify" -@raises @val external stringifyAny: 'a => option = "JSON.stringify" +@deprecated("Use `stringify` with optional parameter instead") @val +external stringifyWithFilter: (t, array) => string = "JSON.stringify" +@deprecated("Use `stringify` with optional parameters instead") @val +external stringifyWithFilterAndIndent: (t, array, int) => string = "JSON.stringify" + @raises @val +external stringifyAny: ('a, ~replacer: replacer=?, ~space: int=?) => option = + "JSON.stringify" +@deprecated("Use `stringifyAny` with optional parameter instead") @raises @val external stringifyAnyWithIndent: ('a, @as(json`null`) _, int) => option = "JSON.stringify" -@raises @val +@deprecated("Use `stringifyAny` with optional parameter instead") @raises @val external stringifyAnyWithReplacer: ('a, (string, t) => t) => option = "JSON.stringify" -@raises @val +@deprecated("Use `stringifyAny` with optional parameters instead") @raises @val external stringifyAnyWithReplacerAndIndent: ('a, (string, t) => t, int) => option = "JSON.stringify" -@raises @val external stringifyAnyWithFilter: ('a, array) => string = "JSON.stringify" -@raises @val +@deprecated("Use `stringifyAny` with optional parameter instead") @raises @val +external stringifyAnyWithFilter: ('a, array) => string = "JSON.stringify" +@deprecated("Use `stringifyAny` with optional parameters instead") @raises @val external stringifyAnyWithFilterAndIndent: ('a, array, int) => string = "JSON.stringify" module Classify = { diff --git a/src/Core__JSON.resi b/src/Core__JSON.resi index 34bf688c..e490091a 100644 --- a/src/Core__JSON.resi +++ b/src/Core__JSON.resi @@ -14,10 +14,14 @@ type rec t = Js.Json.t = | Object(Core__Dict.t) | Array(array) +@unboxed +type replacer = Keys(array) | Replacer((string, t) => t) + /** -`parseExn(string)` +`parseExn(string, ~reviver=?)` Parses a JSON string or throws a JavaScript exception (SyntaxError), if the string isn't valid. +The reviver describes how the value should be transformed. It is a function which receives a key and a value. It returns a JSON type. ## Examples @@ -31,6 +35,28 @@ try { } catch { | Exn.Error(_) => Console.log("error") } + +let reviver = (_, value) => { + let valueType = JSON.Classify.classify(value) + + switch valueType { + | String(string) => string->String.toUpperCase->JSON.Encode.string + | Number(number) => (number *. 2.0)->JSON.Encode.float + | _ => value + } +} + +let jsonString = `{"hello":"world","someNumber":21}` + +try { + JSON.parseExn(jsonString, ~reviver)->Console.log + // { hello: 'WORLD', someNumber: 42 } + + JSON.parseExn("", ~reviver)->Console.log + // error +} catch { +| Exn.Error(_) => Console.log("error") +} ``` ## Exceptions @@ -39,7 +65,7 @@ try { */ @raises(Exn.t) @val -external parseExn: string => t = "JSON.parse" +external parseExn: (string, ~reviver: (string, t) => t=?) => t = "JSON.parse" /** `parseExnWithReviver(string, reviver)` @@ -77,14 +103,17 @@ try { - Raises a SyntaxError if the string isn't valid JSON. */ +@deprecated("Use `parseExn` with optional parameter instead") @raises(Exn.t) @val external parseExnWithReviver: (string, (string, t) => t) => t = "JSON.parse" /** -`stringify(json)` +`stringify(json, ~replacer=?, ~space=?)` Converts a JSON object to a JSON string. +The replacer describes how the value should be transformed. It is a function which receives a key and a value, +or an array of keys which should be included in the output. If you want to stringify any type, use `JSON.stringifyAny` instead. ## Examples @@ -98,10 +127,32 @@ let json = JSON.stringify(json) // {"foo":"bar","hello":"world","someNumber":42} + +JSON.stringify(json, ~space=2) +// { +// "foo": "bar", +// "hello": "world", +// "someNumber": 42 +// } + +JSON.stringify(json, ~replacer=Keys(["foo", "someNumber"])) +// {"foo":"bar","someNumber":42} + +let replacer = JSON.Replacer((_, value) => { + let decodedValue = value->JSON.Decode.string + + switch decodedValue { + | Some(string) => string->String.toUpperCase->JSON.Encode.string + | None => value + } +}) + +JSON.stringify(json, ~replacer) +// {"foo":"BAR","hello":"WORLD","someNumber":42} ``` */ @val -external stringify: t => string = "JSON.stringify" +external stringify: (t, ~replacer: replacer=?, ~space: int=?) => string = "JSON.stringify" /** `stringifyWithIndent(json, indentation)` @@ -126,6 +177,7 @@ JSON.stringifyWithIndent(json, 2) // } ``` */ +@deprecated("Use `stringify` with optional parameter instead") @val external stringifyWithIndent: (t, @as(json`null`) _, int) => string = "JSON.stringify" @@ -158,6 +210,7 @@ JSON.stringifyWithReplacer(json, replacer) // {"foo":"BAR","hello":"WORLD","someNumber":42} ``` */ +@deprecated("Use `stringify` with optional parameter instead") @val external stringifyWithReplacer: (t, (string, t) => t) => string = "JSON.stringify" @@ -194,6 +247,7 @@ JSON.stringifyWithReplacerAndIndent(json, replacer, 2) // } ``` */ +@deprecated("Use `stringify` with optional parameters instead") @val external stringifyWithReplacerAndIndent: (t, (string, t) => t, int) => string = "JSON.stringify" @@ -217,6 +271,7 @@ JSON.stringifyWithFilter(json, ["foo", "someNumber"]) // {"foo":"bar","someNumber":42} ``` */ +@deprecated("Use `stringify` with optional parameter instead") @val external stringifyWithFilter: (t, array) => string = "JSON.stringify" @@ -243,13 +298,15 @@ JSON.stringifyWithFilterAndIndent(json, ["foo", "someNumber"], 2) // } ``` */ +@deprecated("Use `stringify` with optional parameters instead") @val external stringifyWithFilterAndIndent: (t, array, int) => string = "JSON.stringify" /** -`stringifyAny(any)` +`stringifyAny(any, ~replacer=?, ~space=?)` Converts any type to a JSON string. +The replacer describes how the value should be transformed. It is a function which receives a key and a value. Stringifying a function or `undefined` will return `None`. If the value contains circular references or `BigInt`s, the function will throw a JavaScript exception (TypeError). If you want to stringify a JSON object, use `JSON.stringify` instead. @@ -265,6 +322,28 @@ let dict = Dict.fromArray([ JSON.stringifyAny(dict) // {"foo":"bar","hello":"world","someNumber":42} +JSON.stringifyAny(dict, ~space=2) +// { +// "foo": "bar", +// "hello": "world", +// "someNumber": 42 +// } + +JSON.stringifyAny(dict, ~replacer=Keys(["foo", "someNumber"])) +// {"foo":"bar","someNumber":42} + +let replacer = Replacer((_, value) => { + let decodedValue = value->JSON.Decode.string + + switch decodedValue { + | Some(string) => string->String.toUpperCase->JSON.Encode.string + | None => value + } +}) + +JSON.stringifyAny(dict, ~replacer) +// {"foo":"BAR","hello":"WORLD","someNumber":42} + JSON.stringifyAny(() => "hello world") // None @@ -279,7 +358,8 @@ BigInt.fromInt(0)->JSON.stringifyAny */ @raises(Exn.t) @val -external stringifyAny: 'a => option = "JSON.stringify" +external stringifyAny: ('a, ~replacer: replacer=?, ~space: int=?) => option = + "JSON.stringify" /** `stringifyAnyWithIndent(any, indentation)` @@ -316,6 +396,7 @@ BigInt.fromInt(0)->JSON.stringifyAny - Raises a TypeError if the value contains circular references. - Raises a TypeError if the value contains `BigInt`s. */ +@deprecated("Use `stringifyAny` with optional parameter instead") @raises(Exn.t) @val external stringifyAnyWithIndent: ('a, @as(json`null`) _, int) => option = "JSON.stringify" @@ -361,6 +442,7 @@ BigInt.fromInt(0)->JSON.stringifyAny - Raises a TypeError if the value contains circular references. - Raises a TypeError if the value contains `BigInt`s. */ +@deprecated("Use `stringifyAny` with optional parameter instead") @raises @val external stringifyAnyWithReplacer: ('a, (string, t) => t) => option = "JSON.stringify" @@ -410,6 +492,7 @@ BigInt.fromInt(0)->JSON.stringifyAny - Raises a TypeError if the value contains circular references. - Raises a TypeError if the value contains `BigInt`s. */ +@deprecated("Use `stringifyAny` with optional parameters instead") @raises @val external stringifyAnyWithReplacerAndIndent: ('a, (string, t) => t, int) => option = @@ -447,6 +530,7 @@ BigInt.fromInt(0)->JSON.stringifyAny - Raises a TypeError if the value contains circular references. - Raises a TypeError if the value contains `BigInt`s. */ +@deprecated("Use `stringifyAny` with optional parameter instead") @raises @val external stringifyAnyWithFilter: ('a, array) => string = "JSON.stringify" @@ -486,6 +570,7 @@ BigInt.fromInt(0)->JSON.stringifyAny - Raises a TypeError if the value contains circular references. - Raises a TypeError if the value contains `BigInt`s. */ +@deprecated("Use `stringifyAny` with optional parameters instead") @raises @val external stringifyAnyWithFilterAndIndent: ('a, array, int) => string = "JSON.stringify" diff --git a/test/TempTests.mjs b/test/TempTests.mjs index 4239421f..5a6c5551 100644 --- a/test/TempTests.mjs +++ b/test/TempTests.mjs @@ -98,7 +98,7 @@ console.info("JSON"); console.info("---"); -var json = JSON.parse("{\"foo\": \"bar\"}"); +var json = JSON.parse("{\"foo\": \"bar\"}", undefined); var json$1 = Core__JSON.Classify.classify(json); diff --git a/test/Test.mjs b/test/Test.mjs index 1fe28b2a..170e6501 100644 --- a/test/Test.mjs +++ b/test/Test.mjs @@ -13,7 +13,7 @@ function print(value) { if (match === "object" || match === "bigint") { return Util.inspect(value); } else if (match === "string") { - return Core__Option.getExn(JSON.stringify(value)); + return Core__Option.getExn(JSON.stringify(value, undefined, undefined)); } else { return String(value); }