diff --git a/src/Core__JSON.res b/src/Core__JSON.res index 100ae5ee..16f28e36 100644 --- a/src/Core__JSON.res +++ b/src/Core__JSON.res @@ -1,27 +1,25 @@ type t = Js.Json.t -type jsonReviver -external asJsonReviver: 'a => jsonReviver = "%identity" -type jsonReplacer -external asJsonReplacer: 'a => jsonReplacer = "%identity" - -@val external parseExn: string => t = "JSON.parse" -@val external parseExnWithReviver: (string, jsonReviver) => t = "JSON.parse" +@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, jsonReplacer) => string = "JSON.stringify" -@val external stringifyWithReplacerAndIndent: (t, jsonReplacer, int) => string = "JSON.stringify" - -@val external parseToAnyExn: string => 'a = "JSON.parse" -@val external parseToAnyExnWithReviver: (string, jsonReviver) => 'a = "JSON.parse" -@val external stringifyAny: 'a => option = "JSON.stringify" +@val external stringifyWithReplacer: (t, (string, t) => t) => string = "JSON.stringify" @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" +@raises @val external stringifyAnyWithIndent: ('a, @as(json`null`) _, int) => option = "JSON.stringify" -@val -external stringifyAnyWithReplacer: ('a, jsonReplacer) => option = "JSON.stringify" -@val -external stringifyAnyWithReplacerAndIndent: ('a, jsonReplacer, int) => option = +@raises @val +external stringifyAnyWithReplacer: ('a, (string, t) => t) => option = "JSON.stringify" +@raises @val +external stringifyAnyWithReplacerAndIndent: ('a, (string, t) => t, int) => option = "JSON.stringify" +@raises @val external stringifyAnyWithFilter: ('a, array) => string = "JSON.stringify" +@raises @val +external stringifyAnyWithFilterAndIndent: ('a, array, int) => string = "JSON.stringify" module Classify = { type t = diff --git a/src/Core__JSON.resi b/src/Core__JSON.resi index fc086a01..32389d70 100644 --- a/src/Core__JSON.resi +++ b/src/Core__JSON.resi @@ -1,29 +1,492 @@ +/*** +Functions for interacting with JSON. +*/ + +/** +A type representing a JSON object. +*/ type t = Js.Json.t -type jsonReviver -external asJsonReviver: 'a => jsonReviver = "%identity" -type jsonReplacer -external asJsonReplacer: 'a => jsonReplacer = "%identity" +/** +`parseExn(string)` + +Parses a JSON string or throws a JavaScript exception (SyntaxError), if the string isn't valid. +It returns a JSON type. + +## Examples +```rescript +try { + let _ = JSON.parseExn(`{"foo":"bar","hello":"world"}`) + // { foo: 'bar', hello: 'world' } + + let _ = JSON.parseExn("") + // error +} catch { +| Exn.Error(obj) => Console.log("error") +} +``` -@val external parseExn: string => t = "JSON.parse" -@val external parseExnWithReviver: (string, jsonReviver) => 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, jsonReplacer) => string = "JSON.stringify" -@val external stringifyWithReplacerAndIndent: (t, jsonReplacer, int) => string = "JSON.stringify" +## Exceptions -@val external parseToAnyExn: string => 'a = "JSON.parse" -@val external parseToAnyExnWithReviver: (string, jsonReviver) => 'a = "JSON.parse" -@val external stringifyAny: 'a => option = "JSON.stringify" +- Raises a SyntaxError (Exn.t) if the string isn't valid JSON. +*/ +@raises(Exn.t) +@val +external parseExn: string => t = "JSON.parse" + +/** +`parseExnWithReviver(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 +```rescript +let reviver = (key, 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.parseExnWithReviver(jsonString, reviver)->Console.log + // { hello: 'WORLD', someNumber: 42 } + + JSON.parseExnWithReviver("", reviver)->Console.log + // error +} catch { +| Exn.Error(_) => Console.log("error") +} +``` + +## Exceptions + +- Raises a SyntaxError if the string isn't valid JSON. +*/ +@raises(Exn.t) +@val +external parseExnWithReviver: (string, (string, t) => t) => t = "JSON.parse" + +/** +`stringify(json)` + +Converts a JSON object to a JSON string. +If you want to stringify any type, use `JSON.stringifyAny` instead. + +## Examples +```rescript +let json = + Dict.fromArray([ + ("foo", JSON.Encode.string("bar")), + ("hello", JSON.Encode.string("world")), + ("someNumber", JSON.Encode.int(42)), + ])->JSON.Encode.object + +JSON.stringify(json) +// {"foo":"bar","hello":"world","someNumber":42} +``` +*/ +@val +external stringify: t => string = "JSON.stringify" + +/** +`stringifyWithIndent(json, indentation)` + +Converts a JSON object to a JSON string. The output will be indented. +If you want to stringify any type, use `JSON.stringifyAnyWithIndent` instead. + +## Examples +```rescript +let json = + Dict.fromArray([ + ("foo", JSON.Encode.string("bar")), + ("hello", JSON.Encode.string("world")), + ("someNumber", JSON.Encode.int(42)), + ])->JSON.Encode.object + +JSON.stringifyWithIndent(json, 2) +// { +// "foo": "bar", +// "hello": "world", +// "someNumber": 42 +// } +``` +*/ +@val +external stringifyWithIndent: (t, @as(json`null`) _, int) => string = "JSON.stringify" + +/** +`stringifyWithReplacer(json, replacer)` + +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. +If you want to stringify any type, use `JSON.stringifyAnyWithReplacer` instead. + +## Examples +```rescript +let json = + Dict.fromArray([ + ("foo", JSON.Encode.string("bar")), + ("hello", JSON.Encode.string("world")), + ("someNumber", JSON.Encode.int(42)), + ])->JSON.Encode.object + +let replacer = (key, value) => { + let decodedValue = value->JSON.Decode.string + + switch decodedValue { + | Some(string) => string->String.toUpperCase->JSON.Encode.string + | None => value + } +} + +JSON.stringifyWithReplacer(json, replacer) +// {"foo":"BAR","hello":"WORLD","someNumber":42} +``` +*/ +@val +external stringifyWithReplacer: (t, (string, t) => t) => string = "JSON.stringify" + +/** +`stringifyWithReplacerAndIndent(json, replacer, indentation)` + +Converts a JSON object to a JSON string. The output will be indented. +The replacer describes how the value should be transformed. It is a function which receives a key and a value. +If you want to stringify any type, use `JSON.stringifyAnyWithReplacerAndIndent` instead. + +## Examples +```rescript +let json = + Dict.fromArray([ + ("foo", JSON.Encode.string("bar")), + ("hello", JSON.Encode.string("world")), + ("someNumber", JSON.Encode.int(42)), + ])->JSON.Encode.object + +let replacer = (key, value) => { + let decodedValue = value->JSON.Decode.string + + switch decodedValue { + | Some(string) => string->String.toUpperCase->JSON.Encode.string + | None => value + } +} + +JSON.stringifyWithReplacerAndIndent(json, replacer, 2) +// { + "foo": "BAR", + "hello": "WORLD", + "someNumber": 42 +} +``` +*/ +@val +external stringifyWithReplacerAndIndent: (t, (string, t) => t, int) => string = "JSON.stringify" + +/** +`stringifyWithFilter(json, filter)` + +Converts a JSON object to a JSON string. +The filter is an array of keys, which should be included in the output. +If you want to stringify any type, use `JSON.stringifyAnyWithFilter` instead. + +## Examples +```rescript +let json = + Dict.fromArray([ + ("foo", JSON.Encode.string("bar")), + ("hello", JSON.Encode.string("world")), + ("someNumber", JSON.Encode.int(42)), + ])->JSON.Encode.object + +JSON.stringifyWithFilter(json, ["foo", "someNumber"]) +// {"foo":"bar","someNumber":42} +``` +*/ +@val +external stringifyWithFilter: (t, array) => string = "JSON.stringify" + +/** +`stringifyWithFilterAndIndent(json, filter, indentation)` + +Converts a JSON object to a JSON string. The output will be indented. +The filter is an array of keys, which should be included in the output. +If you want to stringify any type, use `JSON.stringifyAnyWithFilterAndIndent` instead. + +## Examples +```rescript +let json = + Dict.fromArray([ + ("foo", JSON.Encode.string("bar")), + ("hello", JSON.Encode.string("world")), + ("someNumber", JSON.Encode.int(42)), + ])->JSON.Encode.object + +JSON.stringifyWithFilterAndIndent(json, ["foo", "someNumber"], 2) +// { +// "foo": "bar", +// "someNumber": 42 +// } +``` +*/ +@val +external stringifyWithFilterAndIndent: (t, array, int) => string = "JSON.stringify" + +/** +`stringifyAny(any)` + +Converts any type to a JSON string. +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. + +## Examples +```rescript +let dict = Dict.fromArray([ + ("foo", JSON.Encode.string("bar")), + ("hello", JSON.Encode.string("world")), + ("someNumber", JSON.Encode.int(42)), +]) + +JSON.stringifyAny(dict) +// {"foo":"bar","hello":"world","someNumber":42} + +JSON.stringifyAny(() => "hello world") +// None + +BigInt.fromInt(0)->JSON.stringifyAny +// exception +``` + +## Exceptions + +- Raises a TypeError if the value contains circular references. +- Raises a TypeError if the value contains `BigInt`s. +*/ +@raises(Exn.t) +@val +external stringifyAny: 'a => option = "JSON.stringify" + +/** +`stringifyAnyWithIndent(any, indentation)` + +Converts any type to a JSON string. The output will be indented. +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.stringifyWithIndent` instead. + +## Examples +```rescript +let dict = Dict.fromArray([ + ("foo", JSON.Encode.string("bar")), + ("hello", JSON.Encode.string("world")), + ("someNumber", JSON.Encode.int(42)), +]) + +JSON.stringifyAnyWithIndent(dict, 2) +// { +// "foo": "bar", +// "hello": "world", +// "someNumber": 42 +// } + +JSON.stringifyAny(() => "hello world") +// None + +BigInt.fromInt(0)->JSON.stringifyAny +// exception +``` + +## Exceptions + +- Raises a TypeError if the value contains circular references. +- Raises a TypeError if the value contains `BigInt`s. +*/ +@raises(Exn.t) @val external stringifyAnyWithIndent: ('a, @as(json`null`) _, int) => option = "JSON.stringify" + +/** +`stringifyAnyWithReplacer(json, replacer)` + +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.stringifyWithReplacer` instead. + +## Examples +```rescript +let dict = Dict.fromArray([ + ("foo", JSON.Encode.string("bar")), + ("hello", JSON.Encode.string("world")), + ("someNumber", JSON.Encode.int(42)), +]) + +let replacer = (key, value) => { + let decodedValue = value->JSON.Decode.string + + switch decodedValue { + | Some(string) => string->String.toUpperCase->JSON.Encode.string + | None => value + } +} + +JSON.stringifyAnyWithReplacer(dict, replacer) +// {"foo":"BAR","hello":"WORLD","someNumber":42} + +JSON.stringifyAny(() => "hello world") +// None + +BigInt.fromInt(0)->JSON.stringifyAny +// exception +``` + +## Exceptions + +- Raises a TypeError if the value contains circular references. +- Raises a TypeError if the value contains `BigInt`s. +*/ +@raises @val -external stringifyAnyWithReplacer: ('a, jsonReplacer) => option = "JSON.stringify" +external stringifyAnyWithReplacer: ('a, (string, t) => t) => option = "JSON.stringify" + +/** +`stringifyAnyWithReplacerAndIndent(json, replacer, indentation)` + +Converts any type to a JSON string. The output will be indented. +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.stringifyWithReplacerAndIndent` instead. + +## Examples +```rescript +let dict = Dict.fromArray([ + ("foo", JSON.Encode.string("bar")), + ("hello", JSON.Encode.string("world")), + ("someNumber", JSON.Encode.int(42)), +]) + +let replacer = (key, value) => { + let decodedValue = value->JSON.Decode.string + + switch decodedValue { + | Some(string) => string->String.toUpperCase->JSON.Encode.string + | None => value + } +} + +JSON.stringifyAnyWithReplacerAndIndent(dict, replacer, 2) +// { +// "foo": "BAR", +// "hello": "WORLD", +// "someNumber": 42 +// } + +JSON.stringifyAny(() => "hello world") +// None + +BigInt.fromInt(0)->JSON.stringifyAny +// exception +``` + +## Exceptions + +- Raises a TypeError if the value contains circular references. +- Raises a TypeError if the value contains `BigInt`s. +*/ +@raises @val -external stringifyAnyWithReplacerAndIndent: ('a, jsonReplacer, int) => option = +external stringifyAnyWithReplacerAndIndent: ('a, (string, t) => t, int) => option = "JSON.stringify" +/** +`stringifyAnyWithFilter(json, filter)` + +Converts any type to a JSON string. +The filter is an array of keys, which should be included in the output. +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.stringifyWithFilter` instead. + +## Examples +```rescript +let dict = Dict.fromArray([ + ("foo", JSON.Encode.string("bar")), + ("hello", JSON.Encode.string("world")), + ("someNumber", JSON.Encode.int(42)), +]) + +JSON.stringifyAnyWithFilter(dict, ["foo", "someNumber"]) +// {"foo": "bar","someNumber": 42} + +JSON.stringifyAny(() => "hello world") +// None + +BigInt.fromInt(0)->JSON.stringifyAny +// exception +``` + +## Exceptions + +- Raises a TypeError if the value contains circular references. +- Raises a TypeError if the value contains `BigInt`s. +*/ +@raises +@val +external stringifyAnyWithFilter: ('a, array) => string = "JSON.stringify" + +/** +`stringifyAnyWithFilterAndIndent(json, filter, indentation)` + +Converts any type to a JSON string. The output will be indented. +The filter is an array of keys, which should be included in the output. +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.stringifyWithFilterAndIndent` instead. + +## Examples +```rescript +let dict = Dict.fromArray([ + ("foo", JSON.Encode.string("bar")), + ("hello", JSON.Encode.string("world")), + ("someNumber", JSON.Encode.int(42)), +]) + +JSON.stringifyAnyWithFilterAndIndent(dict, ["foo", "someNumber"], 2) +// { +// "foo": "bar", +// "someNumber": 42 +// } + +JSON.stringifyAny(() => "hello world") +// None + +BigInt.fromInt(0)->JSON.stringifyAny +// exception +``` + +## Exceptions + +- Raises a TypeError if the value contains circular references. +- Raises a TypeError if the value contains `BigInt`s. +*/ +@raises +@val +external stringifyAnyWithFilterAndIndent: ('a, array, int) => string = "JSON.stringify" + module Classify: { + /** + A type representing a JavaScript type. + */ type t = | Bool(bool) | Null @@ -32,24 +495,182 @@ module Classify: { | Object(Core__Dict.t) | Array(array) + /** + Returns the JSON type of any value. + + ## Examples + ```rescript + JSON.Classify.classify("hello world") + // String("hello world") + + JSON.Classify.classify(42) + // Number(42) + ``` + */ let classify: 'a => t } module Encode: { + /** + Returns a boolean as a JSON object. + + ## Examples + ```rescript + JSON.Encode.bool(true) + ``` + */ external bool: bool => t = "%identity" + + /** + Returns null as a JSON object. + + ## Examples + ```rescript + JSON.Encode.null + ``` + */ external null: t = "#null" + + /** + Returns a string as a JSON object. + + ## Examples + ```rescript + JSON.Encode.string("hello world") + ``` + */ external string: string => t = "%identity" + + /** + Returns an int as a JSON object. + + ## Examples + ```rescript + JSON.Encode.int(42) + ``` + */ external int: int => t = "%identity" + + /** + Returns a float as a JSON object. + + ## Examples + ```rescript + JSON.Encode.float(42.0) + ``` + */ external float: float => t = "%identity" + + /** + Returns a dict as a JSON object. + + ## Examples + ```rescript + let dict = Dict.fromArray([ + ("foo", JSON.Encode.string("bar")), + ("hello", JSON.Encode.string("world")), + ]) + + JSON.Encode.object(dict) + ``` + */ external object: Core__Dict.t => t = "%identity" + + /** + Returns an array as a JSON object. + + ## Examples + ```rescript + let array = [JSON.Encode.string("hello world"), JSON.Encode.int(42)] + + JSON.Encode.array(array) + ``` + */ external array: array => t = "%identity" } module Decode: { + /** + Decodes a single JSON value. If the value is a bool, it will return `Some(bool)` - otherwise it will return `None`. + + ## Examples + ```rescript + JSON.parseExn(`true`)->JSON.Decode.bool + // Some(true) + + JSON.parseExn(`"hello world"`)->JSON.Decode.bool + // None + ``` + */ let bool: t => option + + /** + Decodes a single JSON value. If the value is null, it will return `Some(Null.t)` - otherwise it will return `None`. + + ## Examples + ```rescript + JSON.parseExn(`null`)->JSON.Decode.null + // Some(null) + + JSON.parseExn(`"hello world"`)->JSON.Decode.null + // None + ``` + */ let null: t => option> + + /** + Decodes a single JSON value. If the value is a string, it will return `Some(string)` - otherwise it will return `None`. + + ## Examples + ```rescript + JSON.parseExn(`"hello world"`)->JSON.Decode.string + // Some("hello world") + + JSON.parseExn(`42`)->JSON.Decode.string + // None + ``` + */ let string: t => option + + /** + Decodes a single JSON value. If the value is a float, it will return `Some(float)` - otherwise it will return `None`. + + ## Examples + ```rescript + JSON.parseExn(`42.0`)->JSON.Decode.float + // Some(42.0) + + JSON.parseExn(`"hello world"`)->JSON.Decode.float + // None + ``` + */ let float: t => option + + /** + Decodes a single JSON value. If the value is an object, it will return `Some(Dict.t)` - otherwise it will return `None`. + + ## Examples + ```rescript + JSON.parseExn(`{"foo":"bar"}`)->JSON.Decode.object + // Some({ foo: 'bar' }) + + JSON.parseExn(`"hello world"`)->JSON.Decode.object + // None + ``` + */ let object: t => option> + + /** + Decodes a single JSON value. If the value is an array, it will return `Some(array)` - otherwise it will return `None`. + + ## Examples + ```rescript + JSON.parseExn(`["foo", "bar"]`)->JSON.Decode.array + // Some([ 'foo', 'bar' ]) + + JSON.parseExn(`"hello world"`)->JSON.Decode.array + // None + ``` + */ let array: t => option> }