@kaiko.io/rescript-deser 4.0.0-rc.2 → 4.0.0-rc.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/bs/.bsdeps +1 -1
- package/lib/bs/.compiler.log +2 -2
- package/lib/bs/.ninja_log +26 -18
- package/lib/bs/src/JSON.ast +0 -0
- package/lib/bs/src/JSON.cmi +0 -0
- package/lib/bs/src/JSON.cmt +0 -0
- package/lib/bs/tests/QUnit.cmt +0 -0
- package/lib/bs/tests/index.cmi +0 -0
- package/lib/bs/tests/index.cmt +0 -0
- package/lib/es6/src/JSON.js +16 -38
- package/lib/es6/tests/index.js +1 -5
- package/lib/js/src/JSON.js +16 -38
- package/lib/js/tests/index.js +1 -5
- package/lib/ocaml/.compiler.log +2 -0
- package/lib/ocaml/.ninja_log +7 -0
- package/lib/ocaml/JSON.cmi +0 -0
- package/lib/ocaml/JSON.cmj +0 -0
- package/lib/ocaml/JSON.cmt +0 -0
- package/lib/ocaml/JSON.res +343 -0
- package/lib/ocaml/install.stamp +0 -0
- package/package.json +2 -2
- package/src/JSON.res +9 -37
package/lib/bs/.bsdeps
CHANGED
package/lib/bs/.compiler.log
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
#Start(
|
|
2
|
-
#Done(
|
|
1
|
+
#Start(1700765549343)
|
|
2
|
+
#Done(1700765549343)
|
package/lib/bs/.ninja_log
CHANGED
|
@@ -1,19 +1,27 @@
|
|
|
1
1
|
# ninja log v6
|
|
2
|
-
0
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
10
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
2
|
+
0 7 1700763106509919117 src/JSON.ast 3df97f11bfe4e9b4
|
|
3
|
+
7 9 1700763106513252576 src/JSON.d 54f2190af6eab30f
|
|
4
|
+
9 26 1700763106529919867 src/JSON.cmj f5326fd4af8f39e2
|
|
5
|
+
9 26 1700763106529919867 src/JSON.cmi f5326fd4af8f39e2
|
|
6
|
+
9 26 1700763106529919867 ../es6/src/JSON.js f5326fd4af8f39e2
|
|
7
|
+
0 16 1700763106513252576 src/JSON.cmj 9477bd76994296b7
|
|
8
|
+
0 16 1700763106513252576 src/JSON.cmi 9477bd76994296b7
|
|
9
|
+
0 16 1700763106513252576 ../es6/src/JSON.js 9477bd76994296b7
|
|
10
|
+
0 6 1700765531805623915 tests/QUnit.ast a6a94a20e20f8557
|
|
11
|
+
1 6 1700765531805623915 tests/index.ast 3c91d8240b8bb4b8
|
|
12
|
+
0 8 1700765531808957333 src/JSON.ast 54dacc9e71f78ec4
|
|
13
|
+
6 8 1700765531808957333 tests/QUnit.d 4a2631c992d93ce7
|
|
14
|
+
6 8 1700765531808957333 tests/index.d 5a4db9cdecd186b3
|
|
15
|
+
8 10 1700765531808957333 src/JSON.d cd2d6773c781340f
|
|
16
|
+
8 15 1700765531815624169 tests/QUnit.cmj e2ba8efce8af03b9
|
|
17
|
+
8 15 1700765531815624169 tests/QUnit.cmi e2ba8efce8af03b9
|
|
18
|
+
8 15 1700765531815624169 ../es6/tests/QUnit.js e2ba8efce8af03b9
|
|
19
|
+
8 15 1700765531815624169 ../js/tests/QUnit.js e2ba8efce8af03b9
|
|
20
|
+
10 28 1700765531828957841 src/JSON.cmj 3da9e5f359841f1a
|
|
21
|
+
10 28 1700765531828957841 src/JSON.cmi 3da9e5f359841f1a
|
|
22
|
+
10 28 1700765531828957841 ../es6/src/JSON.js 3da9e5f359841f1a
|
|
23
|
+
10 28 1700765531828957841 ../js/src/JSON.js 3da9e5f359841f1a
|
|
24
|
+
28 38 1700765531838958094 tests/index.cmj 630554c615a94d8d
|
|
25
|
+
28 38 1700765531838958094 tests/index.cmi 630554c615a94d8d
|
|
26
|
+
28 38 1700765531838958094 ../es6/tests/index.js 630554c615a94d8d
|
|
27
|
+
28 38 1700765531838958094 ../js/tests/index.js 630554c615a94d8d
|
package/lib/bs/src/JSON.ast
CHANGED
|
Binary file
|
package/lib/bs/src/JSON.cmi
CHANGED
|
Binary file
|
package/lib/bs/src/JSON.cmt
CHANGED
|
Binary file
|
package/lib/bs/tests/QUnit.cmt
CHANGED
|
Binary file
|
package/lib/bs/tests/index.cmi
CHANGED
|
Binary file
|
package/lib/bs/tests/index.cmt
CHANGED
|
Binary file
|
package/lib/es6/src/JSON.js
CHANGED
|
@@ -110,11 +110,11 @@ function toString(type_) {
|
|
|
110
110
|
case "Array" :
|
|
111
111
|
return "Array of " + toString(type_._0);
|
|
112
112
|
case "Tuple" :
|
|
113
|
-
return "Tuple of (" +
|
|
113
|
+
return "Tuple of (" + type_._0.map(toString).join(", ") + ")";
|
|
114
114
|
case "Object" :
|
|
115
|
-
var desc =
|
|
116
|
-
|
|
117
|
-
|
|
115
|
+
var desc = type_._0.map(function (param) {
|
|
116
|
+
return param[0] + ": " + toString(param[1]);
|
|
117
|
+
}).join(", ");
|
|
118
118
|
return "Object of {" + desc + "}";
|
|
119
119
|
case "Optional" :
|
|
120
120
|
case "OptionalWithDefault" :
|
|
@@ -306,7 +306,7 @@ function fromUntagged(untagged, _shape, self) {
|
|
|
306
306
|
} else {
|
|
307
307
|
if (match.TAG === "JSONArray") {
|
|
308
308
|
var shape$1 = shape._0;
|
|
309
|
-
return
|
|
309
|
+
return match._0.map((function(shape$1){
|
|
310
310
|
return function (item) {
|
|
311
311
|
return fromUntagged(item, shape$1, self);
|
|
312
312
|
}
|
|
@@ -344,7 +344,7 @@ function fromUntagged(untagged, _shape, self) {
|
|
|
344
344
|
} else {
|
|
345
345
|
if (match.TAG === "JSONObject") {
|
|
346
346
|
var values = match._0;
|
|
347
|
-
return Curry._1(Prelude.Dict.fromArray,
|
|
347
|
+
return Curry._1(Prelude.Dict.fromArray, shape._0.map((function(values){
|
|
348
348
|
return function (param) {
|
|
349
349
|
var field = param[0];
|
|
350
350
|
var value;
|
|
@@ -426,9 +426,13 @@ function fromUntagged(untagged, _shape, self) {
|
|
|
426
426
|
exit = 1;
|
|
427
427
|
} else {
|
|
428
428
|
if (match.TAG === "JSONArray") {
|
|
429
|
-
return Prelude.$$Array.
|
|
430
|
-
|
|
431
|
-
|
|
429
|
+
return Prelude.$$Array.keepSome(match._0.map((function(shape){
|
|
430
|
+
return function (param) {
|
|
431
|
+
return Curry._1(shape._0.fromJSON, param);
|
|
432
|
+
}
|
|
433
|
+
}(shape))).map(Prelude.Result.warn)).map(function (prim) {
|
|
434
|
+
return prim;
|
|
435
|
+
});
|
|
432
436
|
}
|
|
433
437
|
exit = 1;
|
|
434
438
|
}
|
|
@@ -518,7 +522,7 @@ function checkFieldsSanity(name, _fields, _optional) {
|
|
|
518
522
|
} else {
|
|
519
523
|
switch (fields.TAG) {
|
|
520
524
|
case "Tuple" :
|
|
521
|
-
return Curry._2(Prelude.ManyResults.map, Prelude.ManyResults.bailU(
|
|
525
|
+
return Curry._2(Prelude.ManyResults.map, Prelude.ManyResults.bailU(fields._0.map((function(optional){
|
|
522
526
|
return function (field, index) {
|
|
523
527
|
return function () {
|
|
524
528
|
return checkFieldsSanity(name + "[" + String(index) + "]", field, optional);
|
|
@@ -528,7 +532,7 @@ function checkFieldsSanity(name, _fields, _optional) {
|
|
|
528
532
|
|
|
529
533
|
}));
|
|
530
534
|
case "Object" :
|
|
531
|
-
return Curry._2(Prelude.ManyResults.map, Prelude.ManyResults.bailU(
|
|
535
|
+
return Curry._2(Prelude.ManyResults.map, Prelude.ManyResults.bailU(fields._0.map((function(optional){
|
|
532
536
|
return function (param) {
|
|
533
537
|
var field = param[1];
|
|
534
538
|
var fieldName = param[0];
|
|
@@ -606,7 +610,7 @@ var Field = {
|
|
|
606
610
|
|
|
607
611
|
function MakeDeserializer(S) {
|
|
608
612
|
var fields = S.fields;
|
|
609
|
-
var name = "Deserializer " + "JSON" + ", " + "File \"JSON.res\", line
|
|
613
|
+
var name = "Deserializer " + "JSON" + ", " + "File \"JSON.res\", line 320, characters 39-47";
|
|
610
614
|
var checkFieldsSanity$1 = function () {
|
|
611
615
|
return checkFieldsSanity(name, fields, false);
|
|
612
616
|
};
|
|
@@ -630,35 +634,9 @@ function MakeDeserializer(S) {
|
|
|
630
634
|
_0: res
|
|
631
635
|
};
|
|
632
636
|
};
|
|
633
|
-
var fromAny = function (any) {
|
|
634
|
-
var match = typeof any;
|
|
635
|
-
if (match === "string") {
|
|
636
|
-
var parsed;
|
|
637
|
-
try {
|
|
638
|
-
parsed = JSON.parse(any);
|
|
639
|
-
}
|
|
640
|
-
catch (exn){
|
|
641
|
-
return {
|
|
642
|
-
TAG: "Error",
|
|
643
|
-
_0: "Could parse string as JSON"
|
|
644
|
-
};
|
|
645
|
-
}
|
|
646
|
-
return fromJSON(parsed);
|
|
647
|
-
}
|
|
648
|
-
try {
|
|
649
|
-
return fromJSON(any);
|
|
650
|
-
}
|
|
651
|
-
catch (exn$1){
|
|
652
|
-
return {
|
|
653
|
-
TAG: "Error",
|
|
654
|
-
_0: "Could not deserialize the data"
|
|
655
|
-
};
|
|
656
|
-
}
|
|
657
|
-
};
|
|
658
637
|
return {
|
|
659
638
|
name: name,
|
|
660
639
|
fromJSON: fromJSON,
|
|
661
|
-
fromAny: fromAny,
|
|
662
640
|
checkFieldsSanity: checkFieldsSanity$1
|
|
663
641
|
};
|
|
664
642
|
}
|
package/lib/es6/tests/index.js
CHANGED
|
@@ -234,11 +234,7 @@ Qunit.module("Recursive deserializer", (function (param) {
|
|
|
234
234
|
"records",
|
|
235
235
|
{
|
|
236
236
|
TAG: "Deserializer",
|
|
237
|
-
_0:
|
|
238
|
-
name: List.name,
|
|
239
|
-
fromJSON: List.fromJSON,
|
|
240
|
-
checkFieldsSanity: List.checkFieldsSanity
|
|
241
|
-
}
|
|
237
|
+
_0: List
|
|
242
238
|
}
|
|
243
239
|
],
|
|
244
240
|
[
|
package/lib/js/src/JSON.js
CHANGED
|
@@ -111,11 +111,11 @@ function toString(type_) {
|
|
|
111
111
|
case "Array" :
|
|
112
112
|
return "Array of " + toString(type_._0);
|
|
113
113
|
case "Tuple" :
|
|
114
|
-
return "Tuple of (" +
|
|
114
|
+
return "Tuple of (" + type_._0.map(toString).join(", ") + ")";
|
|
115
115
|
case "Object" :
|
|
116
|
-
var desc =
|
|
117
|
-
|
|
118
|
-
|
|
116
|
+
var desc = type_._0.map(function (param) {
|
|
117
|
+
return param[0] + ": " + toString(param[1]);
|
|
118
|
+
}).join(", ");
|
|
119
119
|
return "Object of {" + desc + "}";
|
|
120
120
|
case "Optional" :
|
|
121
121
|
case "OptionalWithDefault" :
|
|
@@ -307,7 +307,7 @@ function fromUntagged(untagged, _shape, self) {
|
|
|
307
307
|
} else {
|
|
308
308
|
if (match.TAG === "JSONArray") {
|
|
309
309
|
var shape$1 = shape._0;
|
|
310
|
-
return
|
|
310
|
+
return match._0.map((function(shape$1){
|
|
311
311
|
return function (item) {
|
|
312
312
|
return fromUntagged(item, shape$1, self);
|
|
313
313
|
}
|
|
@@ -345,7 +345,7 @@ function fromUntagged(untagged, _shape, self) {
|
|
|
345
345
|
} else {
|
|
346
346
|
if (match.TAG === "JSONObject") {
|
|
347
347
|
var values = match._0;
|
|
348
|
-
return Curry._1(Prelude.Dict.fromArray,
|
|
348
|
+
return Curry._1(Prelude.Dict.fromArray, shape._0.map((function(values){
|
|
349
349
|
return function (param) {
|
|
350
350
|
var field = param[0];
|
|
351
351
|
var value;
|
|
@@ -427,9 +427,13 @@ function fromUntagged(untagged, _shape, self) {
|
|
|
427
427
|
exit = 1;
|
|
428
428
|
} else {
|
|
429
429
|
if (match.TAG === "JSONArray") {
|
|
430
|
-
return Prelude.$$Array.
|
|
431
|
-
|
|
432
|
-
|
|
430
|
+
return Prelude.$$Array.keepSome(match._0.map((function(shape){
|
|
431
|
+
return function (param) {
|
|
432
|
+
return Curry._1(shape._0.fromJSON, param);
|
|
433
|
+
}
|
|
434
|
+
}(shape))).map(Prelude.Result.warn)).map(function (prim) {
|
|
435
|
+
return prim;
|
|
436
|
+
});
|
|
433
437
|
}
|
|
434
438
|
exit = 1;
|
|
435
439
|
}
|
|
@@ -519,7 +523,7 @@ function checkFieldsSanity(name, _fields, _optional) {
|
|
|
519
523
|
} else {
|
|
520
524
|
switch (fields.TAG) {
|
|
521
525
|
case "Tuple" :
|
|
522
|
-
return Curry._2(Prelude.ManyResults.map, Prelude.ManyResults.bailU(
|
|
526
|
+
return Curry._2(Prelude.ManyResults.map, Prelude.ManyResults.bailU(fields._0.map((function(optional){
|
|
523
527
|
return function (field, index) {
|
|
524
528
|
return function () {
|
|
525
529
|
return checkFieldsSanity(name + "[" + String(index) + "]", field, optional);
|
|
@@ -529,7 +533,7 @@ function checkFieldsSanity(name, _fields, _optional) {
|
|
|
529
533
|
|
|
530
534
|
}));
|
|
531
535
|
case "Object" :
|
|
532
|
-
return Curry._2(Prelude.ManyResults.map, Prelude.ManyResults.bailU(
|
|
536
|
+
return Curry._2(Prelude.ManyResults.map, Prelude.ManyResults.bailU(fields._0.map((function(optional){
|
|
533
537
|
return function (param) {
|
|
534
538
|
var field = param[1];
|
|
535
539
|
var fieldName = param[0];
|
|
@@ -607,7 +611,7 @@ var Field = {
|
|
|
607
611
|
|
|
608
612
|
function MakeDeserializer(S) {
|
|
609
613
|
var fields = S.fields;
|
|
610
|
-
var name = "Deserializer " + "JSON" + ", " + "File \"JSON.res\", line
|
|
614
|
+
var name = "Deserializer " + "JSON" + ", " + "File \"JSON.res\", line 320, characters 39-47";
|
|
611
615
|
var checkFieldsSanity$1 = function () {
|
|
612
616
|
return checkFieldsSanity(name, fields, false);
|
|
613
617
|
};
|
|
@@ -631,35 +635,9 @@ function MakeDeserializer(S) {
|
|
|
631
635
|
_0: res
|
|
632
636
|
};
|
|
633
637
|
};
|
|
634
|
-
var fromAny = function (any) {
|
|
635
|
-
var match = typeof any;
|
|
636
|
-
if (match === "string") {
|
|
637
|
-
var parsed;
|
|
638
|
-
try {
|
|
639
|
-
parsed = JSON.parse(any);
|
|
640
|
-
}
|
|
641
|
-
catch (exn){
|
|
642
|
-
return {
|
|
643
|
-
TAG: "Error",
|
|
644
|
-
_0: "Could parse string as JSON"
|
|
645
|
-
};
|
|
646
|
-
}
|
|
647
|
-
return fromJSON(parsed);
|
|
648
|
-
}
|
|
649
|
-
try {
|
|
650
|
-
return fromJSON(any);
|
|
651
|
-
}
|
|
652
|
-
catch (exn$1){
|
|
653
|
-
return {
|
|
654
|
-
TAG: "Error",
|
|
655
|
-
_0: "Could not deserialize the data"
|
|
656
|
-
};
|
|
657
|
-
}
|
|
658
|
-
};
|
|
659
638
|
return {
|
|
660
639
|
name: name,
|
|
661
640
|
fromJSON: fromJSON,
|
|
662
|
-
fromAny: fromAny,
|
|
663
641
|
checkFieldsSanity: checkFieldsSanity$1
|
|
664
642
|
};
|
|
665
643
|
}
|
package/lib/js/tests/index.js
CHANGED
|
@@ -235,11 +235,7 @@ Qunit.module("Recursive deserializer", (function (param) {
|
|
|
235
235
|
"records",
|
|
236
236
|
{
|
|
237
237
|
TAG: "Deserializer",
|
|
238
|
-
_0:
|
|
239
|
-
name: List.name,
|
|
240
|
-
fromJSON: List.fromJSON,
|
|
241
|
-
checkFieldsSanity: List.checkFieldsSanity
|
|
242
|
-
}
|
|
238
|
+
_0: List
|
|
243
239
|
}
|
|
244
240
|
],
|
|
245
241
|
[
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# ninja log v6
|
|
2
|
+
0 3 1700763106533253326 JSON.cmi bafb789a597a23fb
|
|
3
|
+
0 3 1700763106533253326 JSON.cmj e0df0c7e15c09f8e
|
|
4
|
+
0 3 1700763106533253326 JSON.cmt cb51be205f02087e
|
|
5
|
+
1 3 1700763106533253326 JSON.res b4c17270ea3dc72d
|
|
6
|
+
3 4 1700763106536586783 install.stamp cff5a5b4c02d30cf
|
|
7
|
+
0 2 1700763494803628812 JSON.cmt cb51be205f02087e
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
@@uncurried
|
|
2
|
+
open Prelude
|
|
3
|
+
|
|
4
|
+
module FieldValue = {
|
|
5
|
+
type t
|
|
6
|
+
external string: string => t = "%identity"
|
|
7
|
+
external int: int => t = "%identity"
|
|
8
|
+
external float: float => t = "%identity"
|
|
9
|
+
external boolean: bool => t = "%identity"
|
|
10
|
+
external array: array<t> => t = "%identity"
|
|
11
|
+
external object: Dict.t<t> => t = "%identity"
|
|
12
|
+
external mapping: Dict.t<t> => t = "%identity"
|
|
13
|
+
external any: 'a => t = "%identity"
|
|
14
|
+
@val external null: t = "undefined"
|
|
15
|
+
|
|
16
|
+
external asString: t => string = "%identity"
|
|
17
|
+
external asInt: t => int = "%identity"
|
|
18
|
+
external asFloat: t => float = "%identity"
|
|
19
|
+
external asBoolean: t => bool = "%identity"
|
|
20
|
+
external asArray: t => array<'a> = "%identity"
|
|
21
|
+
external asObject: t => 'a = "%identity"
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
exception TypeError(string)
|
|
25
|
+
|
|
26
|
+
@doc("The module type of a built deserializer which is suitable to add as a subparser.")
|
|
27
|
+
module type Deserializer = {
|
|
28
|
+
type t
|
|
29
|
+
let name: string
|
|
30
|
+
let fromJSON: Js.Json.t => result<t, string>
|
|
31
|
+
let checkFieldsSanity: unit => result<unit, string>
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
module Field = {
|
|
35
|
+
type rec t =
|
|
36
|
+
| Any
|
|
37
|
+
| String
|
|
38
|
+
| Literal(string)
|
|
39
|
+
| Int
|
|
40
|
+
| Float
|
|
41
|
+
| Boolean
|
|
42
|
+
| Array(t)
|
|
43
|
+
/// These SHOULD strings in ISO format, but we only validate the string
|
|
44
|
+
/// can be represented in Js.Date without spewing NaN all over the place;
|
|
45
|
+
/// Js.Date.fromString("xxx") returns an object that is mostly unusable.
|
|
46
|
+
///
|
|
47
|
+
/// We also allow floats and then use Js.Date.fromFloat.
|
|
48
|
+
| Date
|
|
49
|
+
| Datetime // alias of Date
|
|
50
|
+
|
|
51
|
+
| Tuple(array<t>)
|
|
52
|
+
| Object(array<(string, t)>)
|
|
53
|
+
| Optional(t)
|
|
54
|
+
| OptionalWithDefault(t, FieldValue.t)
|
|
55
|
+
/// An arbitrary mapping from names to other arbitrary fields. The
|
|
56
|
+
/// difference with Object, is that you don't know the names of the
|
|
57
|
+
/// expected entries.
|
|
58
|
+
| Mapping(t)
|
|
59
|
+
|
|
60
|
+
| Deserializer(module(Deserializer))
|
|
61
|
+
|
|
62
|
+
/// A specialized Array of deserialized items that ignores unparsable
|
|
63
|
+
/// items and returns the valid collection. This saves the user from
|
|
64
|
+
/// writing 'Array(DefaultWhenInvalid(Optional(Deserializer(module(M)))))'
|
|
65
|
+
/// and then post-process the list of items with 'Array.keepSome'
|
|
66
|
+
| Collection(module(Deserializer))
|
|
67
|
+
| DefaultWhenInvalid(t, FieldValue.t)
|
|
68
|
+
|
|
69
|
+
// FIXME: this is used to add additional restrictions like variadictInt or
|
|
70
|
+
// variadicString; but I find it too type-unsafe. I might consider having
|
|
71
|
+
// a Constraints for this in the future.
|
|
72
|
+
| Morphism(t, FieldValue.t => FieldValue.t)
|
|
73
|
+
|
|
74
|
+
| Self
|
|
75
|
+
|
|
76
|
+
let usingString = (f: string => 'a) => value => value->FieldValue.asString->f->FieldValue.any
|
|
77
|
+
let usingInt = (f: int => 'a) => value => value->FieldValue.asInt->f->FieldValue.any
|
|
78
|
+
let usingFloat = (f: float => 'a) => value => value->FieldValue.asFloat->f->FieldValue.any
|
|
79
|
+
let usingBoolean = (f: bool => 'a) => value => value->FieldValue.asBoolean->f->FieldValue.any
|
|
80
|
+
let usingArray = (f: array<'a> => 'b) => value => value->FieldValue.asArray->f->FieldValue.any
|
|
81
|
+
let usingObject = (f: 'a => 'b) => value => value->FieldValue.asObject->f->FieldValue.any
|
|
82
|
+
|
|
83
|
+
let variadicInt = (hint: string, fromJs: int => option<'variadicType>) => Morphism(
|
|
84
|
+
Int,
|
|
85
|
+
usingInt(i => {
|
|
86
|
+
switch i->fromJs {
|
|
87
|
+
| Some(internalValue) => internalValue
|
|
88
|
+
| None =>
|
|
89
|
+
raise(TypeError(`This Int(${i->Int.toString}) not a valid value here. Hint: ${hint}`))
|
|
90
|
+
}
|
|
91
|
+
}),
|
|
92
|
+
)
|
|
93
|
+
let variadicString = (hint: string, fromJs: string => option<'variadicType>) => Morphism(
|
|
94
|
+
String,
|
|
95
|
+
usingString(i => {
|
|
96
|
+
switch i->fromJs {
|
|
97
|
+
| Some(internalValue) => internalValue
|
|
98
|
+
| None => raise(TypeError(`This String("${i}") not a valid value here. Hint: ${hint}`))
|
|
99
|
+
}
|
|
100
|
+
}),
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
let rec toString = (type_: t) =>
|
|
104
|
+
switch type_ {
|
|
105
|
+
| Any => "Any"
|
|
106
|
+
| String => "String"
|
|
107
|
+
| Literal(lit) => `Literal: ${lit}`
|
|
108
|
+
| Int => "Integer"
|
|
109
|
+
| Float => "Float"
|
|
110
|
+
| Boolean => "Boolean"
|
|
111
|
+
| Datetime
|
|
112
|
+
| Date => "Date"
|
|
113
|
+
| Self => "Self (recursive)"
|
|
114
|
+
| Collection(m) => {
|
|
115
|
+
module M = unpack(m: Deserializer)
|
|
116
|
+
"Collection of " ++ M.name
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
| Array(t) => "Array of " ++ t->toString
|
|
120
|
+
| Tuple(bases) => `Tuple of (${bases->Array.map(toString)->Array.join(", ")})`
|
|
121
|
+
| Object(fields) => {
|
|
122
|
+
let desc = fields->Array.map(((field, t)) => `${field}: ${t->toString}`)->Array.join(", ")
|
|
123
|
+
`Object of {${desc}}`
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
| OptionalWithDefault(t, _)
|
|
127
|
+
| Optional(t) =>
|
|
128
|
+
"Null of " ++ t->toString
|
|
129
|
+
| Mapping(t) => `Mapping of ${t->toString}`
|
|
130
|
+
| Morphism(t, _) => t->toString ++ " to apply a morphism"
|
|
131
|
+
| Deserializer(m) => {
|
|
132
|
+
module M = unpack(m: Deserializer)
|
|
133
|
+
M.name
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
| DefaultWhenInvalid(t, _) => `Protected ${t->toString}`
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
let _taggedToString = (tagged: Js.Json.tagged_t) => {
|
|
140
|
+
switch tagged {
|
|
141
|
+
| Js.Json.JSONFalse => "Boolean(false)"
|
|
142
|
+
| Js.Json.JSONTrue => "Boolean(true)"
|
|
143
|
+
| Js.Json.JSONNull => "Null"
|
|
144
|
+
| Js.Json.JSONString(text) => `String("${text}")`
|
|
145
|
+
| Js.Json.JSONNumber(number) => `Number(${number->Float.toString})`
|
|
146
|
+
| Js.Json.JSONObject(obj) => `Object(${obj->Js.Json.stringifyAny->default("...")})`
|
|
147
|
+
| Js.Json.JSONArray(array) => `Array(${array->Js.Json.stringifyAny->default("...")})`
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
let rec extractValue = (
|
|
152
|
+
values: Dict.t<Js.Json.t>,
|
|
153
|
+
field: string,
|
|
154
|
+
shape: t,
|
|
155
|
+
self: t,
|
|
156
|
+
): FieldValue.t => {
|
|
157
|
+
switch values->Dict.get(field) {
|
|
158
|
+
| Some(value) => value->fromUntagged(shape, self)
|
|
159
|
+
| None =>
|
|
160
|
+
switch shape {
|
|
161
|
+
| DefaultWhenInvalid(_, _) => Js.Json.null->fromUntagged(shape, self)
|
|
162
|
+
| Optional(_) => Js.Json.null->fromUntagged(shape, self)
|
|
163
|
+
| OptionalWithDefault(_, default) => default
|
|
164
|
+
| _ => raise(TypeError(`Missing non-optional field '${field}'`))
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
and fromUntagged = (untagged: Js.Json.t, shape: t, self: t): FieldValue.t => {
|
|
169
|
+
switch (shape, untagged->Js.Json.classify) {
|
|
170
|
+
| (Any, _) => untagged->FieldValue.any
|
|
171
|
+
| (Literal(expected), Js.Json.JSONString(text)) =>
|
|
172
|
+
if text == expected {
|
|
173
|
+
FieldValue.string(text)
|
|
174
|
+
} else {
|
|
175
|
+
raise(TypeError(`Expecting literal ${expected}, got ${text}`))
|
|
176
|
+
}
|
|
177
|
+
| (String, Js.Json.JSONString(text)) => FieldValue.string(text)
|
|
178
|
+
| (Int, Js.Json.JSONNumber(number)) => FieldValue.int(number->Float.toInt)
|
|
179
|
+
| (Float, Js.Json.JSONNumber(number)) => FieldValue.float(number)
|
|
180
|
+
| (Boolean, Js.Json.JSONTrue) => FieldValue.boolean(true)
|
|
181
|
+
| (Boolean, Js.Json.JSONFalse) => FieldValue.boolean(false)
|
|
182
|
+
| (Tuple(bases), Js.Json.JSONArray(items)) => {
|
|
183
|
+
let lenbases = bases->Array.length
|
|
184
|
+
let lenitems = items->Array.length
|
|
185
|
+
if lenbases == lenitems {
|
|
186
|
+
let values = Array.zipBy(items, bases, (i, b) => fromUntagged(i, b, self))
|
|
187
|
+
values->FieldValue.array
|
|
188
|
+
} else {
|
|
189
|
+
raise(
|
|
190
|
+
TypeError(`Expecting ${lenbases->Int.toString} items, got ${lenitems->Int.toString}`),
|
|
191
|
+
)
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
| (Datetime, Js.Json.JSONString(s))
|
|
196
|
+
| (Date, Js.Json.JSONString(s)) => {
|
|
197
|
+
let r = Js.Date.fromString(s)
|
|
198
|
+
if r->Js.Date.getDate->Js.Float.isNaN {
|
|
199
|
+
raise(TypeError(`Invalid date ${s}`))
|
|
200
|
+
}
|
|
201
|
+
r->FieldValue.any
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
| (Datetime, Js.Json.JSONNumber(f))
|
|
205
|
+
| (Date, Js.Json.JSONNumber(f)) => {
|
|
206
|
+
let r = Js.Date.fromFloat(f)
|
|
207
|
+
if r->Js.Date.getDate->Js.Float.isNaN {
|
|
208
|
+
raise(TypeError(`Invalid date ${f->Js.Float.toString}`))
|
|
209
|
+
}
|
|
210
|
+
r->FieldValue.any
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
| (Array(shape), Js.Json.JSONArray(items)) =>
|
|
214
|
+
FieldValue.array(items->Array.map(item => item->fromUntagged(shape, self)))
|
|
215
|
+
| (Mapping(f), Js.Json.JSONObject(values)) =>
|
|
216
|
+
values->Dict.mapValues(v => v->fromUntagged(f, self))->FieldValue.mapping
|
|
217
|
+
| (Object(fields), Js.Json.JSONObject(values)) =>
|
|
218
|
+
FieldValue.object(
|
|
219
|
+
fields
|
|
220
|
+
->Array.map(((field, shape)) => {
|
|
221
|
+
let value = switch extractValue(values, field, shape, self) {
|
|
222
|
+
| value => value
|
|
223
|
+
| exception TypeError(msg) => raise(TypeError(`Field "${field}": ${msg}`))
|
|
224
|
+
}
|
|
225
|
+
(field, value)
|
|
226
|
+
})
|
|
227
|
+
->Dict.fromArray,
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
| (OptionalWithDefault(_, value), Js.Json.JSONNull) => value
|
|
231
|
+
| (OptionalWithDefault(shape, _), _) => untagged->fromUntagged(shape, self)
|
|
232
|
+
| (Optional(_), Js.Json.JSONNull) => FieldValue.null
|
|
233
|
+
| (Optional(shape), _) => untagged->fromUntagged(shape, self)
|
|
234
|
+
| (Morphism(shape, f), _) => untagged->fromUntagged(shape, self)->f->FieldValue.any
|
|
235
|
+
|
|
236
|
+
| (Collection(m), Js.Json.JSONArray(items)) => {
|
|
237
|
+
module M = unpack(m: Deserializer)
|
|
238
|
+
items
|
|
239
|
+
->Array.map(M.fromJSON)
|
|
240
|
+
->Array.map(Result.warn)
|
|
241
|
+
->Array.keepSome
|
|
242
|
+
->Array.map(FieldValue.any)
|
|
243
|
+
->FieldValue.array
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
| (Deserializer(m), _) => {
|
|
247
|
+
module M = unpack(m: Deserializer)
|
|
248
|
+
switch untagged->M.fromJSON {
|
|
249
|
+
| Ok(res) => res->FieldValue.any
|
|
250
|
+
| Error(msg) => raise(TypeError(msg))
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
| (DefaultWhenInvalid(t, default), _) =>
|
|
255
|
+
switch untagged->fromUntagged(t, self) {
|
|
256
|
+
| res => res
|
|
257
|
+
| exception TypeError(msg) => {
|
|
258
|
+
Js.Console.warn2("Detected and ignore (with default): ", msg)
|
|
259
|
+
default
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
| (Self, _) => untagged->fromUntagged(self, self)
|
|
263
|
+
| (expected, actual) =>
|
|
264
|
+
raise(TypeError(`Expected ${expected->toString}, but got ${actual->_taggedToString} instead`))
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
let rec checkFieldsSanity = (name: string, fields: t, optional: bool): result<unit, string> =>
|
|
269
|
+
switch (fields, optional) {
|
|
270
|
+
| (Self, false) => Error(`${name}: Trivial infinite recursion 'let fields = Self'`)
|
|
271
|
+
| (Self, true) => Ok()
|
|
272
|
+
|
|
273
|
+
| (Any, _) => Ok()
|
|
274
|
+
| (String, _) | (Float, _) | (Int, _) | (Literal(_), _) => Ok()
|
|
275
|
+
| (Boolean, _) | (Date, _) | (Datetime, _) => Ok()
|
|
276
|
+
| (Morphism(_, _), _) => Ok()
|
|
277
|
+
|
|
278
|
+
| (Collection(mod), _)
|
|
279
|
+
| (Deserializer(mod), _) => {
|
|
280
|
+
module M = unpack(mod: Deserializer)
|
|
281
|
+
switch M.checkFieldsSanity() {
|
|
282
|
+
| Ok() => Ok()
|
|
283
|
+
| Error(msg) => Error(`${name}/ ${msg}`)
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
| (DefaultWhenInvalid(fields, _), _)
|
|
288
|
+
| (OptionalWithDefault(fields, _), _)
|
|
289
|
+
| (Optional(fields), _) =>
|
|
290
|
+
checkFieldsSanity(name, fields, true)
|
|
291
|
+
|
|
292
|
+
| (Object(fields), optional) =>
|
|
293
|
+
fields
|
|
294
|
+
->Array.map(((fieldName, field)) => () =>
|
|
295
|
+
checkFieldsSanity(`${name}::${fieldName}`, field, optional))
|
|
296
|
+
->ManyResults.bailU
|
|
297
|
+
->ManyResults.map(_ => ())
|
|
298
|
+
|
|
299
|
+
/// Mappings and arrays can be empty, so their payloads are
|
|
300
|
+
/// automatically optional.
|
|
301
|
+
| (Mapping(field), _) | (Array(field), _) => checkFieldsSanity(name, field, true)
|
|
302
|
+
|
|
303
|
+
| (Tuple(fields), optional) =>
|
|
304
|
+
fields
|
|
305
|
+
->Array.mapWithIndex((field, index) => () =>
|
|
306
|
+
checkFieldsSanity(`${name}[${index->Int.toString}]`, field, optional))
|
|
307
|
+
->ManyResults.bailU
|
|
308
|
+
->ManyResults.map(_ => ())
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
module type Serializable = {
|
|
313
|
+
type t
|
|
314
|
+
let fields: Field.t
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
module MakeDeserializer = (S: Serializable): (Deserializer with type t = S.t) => {
|
|
318
|
+
type t = S.t
|
|
319
|
+
let fields = S.fields
|
|
320
|
+
%%private(let (loc, _f) = __LOC_OF__(module(S: Serializable)))
|
|
321
|
+
let name = `Deserializer ${__MODULE__}, ${loc}`
|
|
322
|
+
|
|
323
|
+
%%private(external _toNativeType: FieldValue.t => t = "%identity")
|
|
324
|
+
|
|
325
|
+
@doc("Checks for trivial infinite-recursion in the fields of the module.
|
|
326
|
+
|
|
327
|
+
Notice this algorithm is just an heuristic, and it might happen that are
|
|
328
|
+
cases of infinite-recursion not detected and cases where detection is a
|
|
329
|
+
false positive.
|
|
330
|
+
|
|
331
|
+
You should use this only while debugging/developing to verify your data.
|
|
332
|
+
|
|
333
|
+
")
|
|
334
|
+
let checkFieldsSanity = () => Field.checkFieldsSanity(name, fields, false)
|
|
335
|
+
|
|
336
|
+
@doc("Parse a `Js.Json.t` into `result<t, string>`")
|
|
337
|
+
let fromJSON = (json: Js.Json.t): result<t, _> => {
|
|
338
|
+
switch json->Field.fromUntagged(fields, fields) {
|
|
339
|
+
| res => Ok(res->_toNativeType)
|
|
340
|
+
| exception TypeError(e) => Error(e)
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
}
|
|
File without changes
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kaiko.io/rescript-deser",
|
|
3
|
-
"version": "4.0.0-rc.
|
|
3
|
+
"version": "4.0.0-rc.4",
|
|
4
4
|
"keywords": [
|
|
5
5
|
"json",
|
|
6
6
|
"deserializer",
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"README.md"
|
|
21
21
|
],
|
|
22
22
|
"dependencies": {
|
|
23
|
-
"@kaiko.io/rescript-prelude": "7.0.0-rc.
|
|
23
|
+
"@kaiko.io/rescript-prelude": "7.0.0-rc.4"
|
|
24
24
|
},
|
|
25
25
|
"devDependencies": {
|
|
26
26
|
"esbuild": "^0.15.7",
|
package/src/JSON.res
CHANGED
|
@@ -23,20 +23,11 @@ module FieldValue = {
|
|
|
23
23
|
|
|
24
24
|
exception TypeError(string)
|
|
25
25
|
|
|
26
|
-
@doc("The module type of a deserializer which is suitable to add as a subparser.")
|
|
27
|
-
module type ArgDeserializer = {
|
|
28
|
-
type t
|
|
29
|
-
let name: string
|
|
30
|
-
let fromJSON: Js.Json.t => result<t, string>
|
|
31
|
-
let checkFieldsSanity: unit => result<unit, string>
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
@doc("The module type of a built deserializer.")
|
|
26
|
+
@doc("The module type of a built deserializer which is suitable to add as a subparser.")
|
|
35
27
|
module type Deserializer = {
|
|
36
28
|
type t
|
|
37
29
|
let name: string
|
|
38
30
|
let fromJSON: Js.Json.t => result<t, string>
|
|
39
|
-
let fromAny: 'a => result<t, string>
|
|
40
31
|
let checkFieldsSanity: unit => result<unit, string>
|
|
41
32
|
}
|
|
42
33
|
|
|
@@ -66,13 +57,13 @@ module Field = {
|
|
|
66
57
|
/// expected entries.
|
|
67
58
|
| Mapping(t)
|
|
68
59
|
|
|
69
|
-
| Deserializer(module(
|
|
60
|
+
| Deserializer(module(Deserializer))
|
|
70
61
|
|
|
71
62
|
/// A specialized Array of deserialized items that ignores unparsable
|
|
72
63
|
/// items and returns the valid collection. This saves the user from
|
|
73
64
|
/// writing 'Array(DefaultWhenInvalid(Optional(Deserializer(module(M)))))'
|
|
74
65
|
/// and then post-process the list of items with 'Array.keepSome'
|
|
75
|
-
| Collection(module(
|
|
66
|
+
| Collection(module(Deserializer))
|
|
76
67
|
| DefaultWhenInvalid(t, FieldValue.t)
|
|
77
68
|
|
|
78
69
|
// FIXME: this is used to add additional restrictions like variadictInt or
|
|
@@ -121,7 +112,7 @@ module Field = {
|
|
|
121
112
|
| Date => "Date"
|
|
122
113
|
| Self => "Self (recursive)"
|
|
123
114
|
| Collection(m) => {
|
|
124
|
-
module M = unpack(m:
|
|
115
|
+
module M = unpack(m: Deserializer)
|
|
125
116
|
"Collection of " ++ M.name
|
|
126
117
|
}
|
|
127
118
|
|
|
@@ -138,7 +129,7 @@ module Field = {
|
|
|
138
129
|
| Mapping(t) => `Mapping of ${t->toString}`
|
|
139
130
|
| Morphism(t, _) => t->toString ++ " to apply a morphism"
|
|
140
131
|
| Deserializer(m) => {
|
|
141
|
-
module M = unpack(m:
|
|
132
|
+
module M = unpack(m: Deserializer)
|
|
142
133
|
M.name
|
|
143
134
|
}
|
|
144
135
|
|
|
@@ -243,7 +234,7 @@ module Field = {
|
|
|
243
234
|
| (Morphism(shape, f), _) => untagged->fromUntagged(shape, self)->f->FieldValue.any
|
|
244
235
|
|
|
245
236
|
| (Collection(m), Js.Json.JSONArray(items)) => {
|
|
246
|
-
module M = unpack(m:
|
|
237
|
+
module M = unpack(m: Deserializer)
|
|
247
238
|
items
|
|
248
239
|
->Array.map(M.fromJSON)
|
|
249
240
|
->Array.map(Result.warn)
|
|
@@ -253,7 +244,7 @@ module Field = {
|
|
|
253
244
|
}
|
|
254
245
|
|
|
255
246
|
| (Deserializer(m), _) => {
|
|
256
|
-
module M = unpack(m:
|
|
247
|
+
module M = unpack(m: Deserializer)
|
|
257
248
|
switch untagged->M.fromJSON {
|
|
258
249
|
| Ok(res) => res->FieldValue.any
|
|
259
250
|
| Error(msg) => raise(TypeError(msg))
|
|
@@ -286,7 +277,7 @@ module Field = {
|
|
|
286
277
|
|
|
287
278
|
| (Collection(mod), _)
|
|
288
279
|
| (Deserializer(mod), _) => {
|
|
289
|
-
module M = unpack(mod:
|
|
280
|
+
module M = unpack(mod: Deserializer)
|
|
290
281
|
switch M.checkFieldsSanity() {
|
|
291
282
|
| Ok() => Ok()
|
|
292
283
|
| Error(msg) => Error(`${name}/ ${msg}`)
|
|
@@ -329,10 +320,7 @@ module MakeDeserializer = (S: Serializable): (Deserializer with type t = S.t) =>
|
|
|
329
320
|
%%private(let (loc, _f) = __LOC_OF__(module(S: Serializable)))
|
|
330
321
|
let name = `Deserializer ${__MODULE__}, ${loc}`
|
|
331
322
|
|
|
332
|
-
%%private(
|
|
333
|
-
external _toNativeType: FieldValue.t => t = "%identity"
|
|
334
|
-
external cast: 'a => 'b = "%identity"
|
|
335
|
-
)
|
|
323
|
+
%%private(external _toNativeType: FieldValue.t => t = "%identity")
|
|
336
324
|
|
|
337
325
|
@doc("Checks for trivial infinite-recursion in the fields of the module.
|
|
338
326
|
|
|
@@ -352,20 +340,4 @@ module MakeDeserializer = (S: Serializable): (Deserializer with type t = S.t) =>
|
|
|
352
340
|
| exception TypeError(e) => Error(e)
|
|
353
341
|
}
|
|
354
342
|
}
|
|
355
|
-
|
|
356
|
-
@doc("Try to parse anything")
|
|
357
|
-
let fromAny = any => {
|
|
358
|
-
switch Js.typeof(any) {
|
|
359
|
-
| "string" =>
|
|
360
|
-
switch any->cast->Js.Json.parseExn {
|
|
361
|
-
| parsed => fromJSON(parsed)
|
|
362
|
-
| exception _ => Error("Could parse string as JSON")
|
|
363
|
-
}
|
|
364
|
-
| _ =>
|
|
365
|
-
switch any->cast->fromJSON {
|
|
366
|
-
| result => result
|
|
367
|
-
| exception _ => Error("Could not deserialize the data")
|
|
368
|
-
}
|
|
369
|
-
}
|
|
370
|
-
}
|
|
371
343
|
}
|