Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
85 changes: 85 additions & 0 deletions jscomp/others/js_json.ml
Original file line number Diff line number Diff line change
Expand Up @@ -129,3 +129,88 @@ external stringify: t -> string = "stringify"
[@@bs.val] [@@bs.scope "JSON"]
external stringifyWithSpace: t -> (_ [@bs.as {json|null|json}]) -> int -> string = "stringify"
[@@bs.val] [@@bs.scope "JSON"]

type preJson
let _toJson: preJson -> t = fun%raw data -> {|
if (!data) {
return data
} else if (typeof data === 'function') {
throw new Error("Cannot serialize a function")
} else if (Array.isArray(data)) {
if (data.tag != null) {
return {
$$tag: data.tag,
$$contents: data.map(_toJson),
$$bsVariant: data[Symbol.for('BsVariant')],
}
} else if (data[Symbol.for('BsVariant')] != null) {
return {
$$bsVariant: data[Symbol.for('BsVariant')],
$$contents: data.map(_toJson)
}
} else if (data[Symbol.for('BsLocalModule')] != null) {
return {
$$bsLocalModule: data[Symbol.for('BsLocalModule')],
$$contents: data.map(_toJson)
}
} else if (data[Symbol.for('BsPolyVar')] != null) {
return {
$$bsPolyVar: data[Symbol.for('BsPolyVar')],
$$contents: data.map(_toJson)
}
} else if (data[Symbol.for('BsRecord')] != null) {
return {
$$bsRecord: data[Symbol.for('BsRecord')],
$$contents: data.map(_toJson)
}
} else {
return data.map(_toJson)
}
} else if (typeof data == 'object') {
var result = {}
Object.keys(data).forEach(key => result[key] = _toJson(data[key]))
return result
} else {
return data
}
|}

(** This dance is required to appease the type checker -- doing
* `toJson: 'a -> t = fun%raw` gives the error "contains type variables
* that cannot be generalized". *)
external toT : 'a -> preJson = "%identity"
let serializeAnyToJson data = _toJson (toT data)

let unserializeAnyFromJsonUnsafe: t -> 'a = fun%raw data -> {|
if (!data) {
return data
} else if (typeof data == 'object') {
if (Array.isArray(data)) {
return data.map(unserializeAnyFromJsonUnsafe)
} else if (data.$$contents) {
var result = data.$$contents.map(unserializeAnyFromJsonUnsafe)
if (data.$$tag != null) {
result.tag = data.$$tag
}
if (data.$$bsRecord) {
result[Symbol.for('BsRecord')] = data.$$bsRecord
}
if (data.$$bsPolyVar) {
result[Symbol.for('BsPolyVar')] = data.$$bsPolyVar
}
if (data.$$bsVariant) {
result[Symbol.for('BsVariant')] = data.$$bsVariant
}
if (data.$$bsLocalModule) {
result[Symbol.for('BsLocalModule')] = data.$$bsLocalModule
}
return result
} else {
var result = {}
Object.keys(data).forEach(key => result[key] = unserializeAnyFromJsonUnsafe(data[key]))
return result
}
} else {
return data
}
|}
21 changes: 21 additions & 0 deletions jscomp/others/js_json.mli
Original file line number Diff line number Diff line change
Expand Up @@ -236,4 +236,25 @@ Js.log \@\@ Js.Json.stringify [| "foo"; "bar" |]
*)


val serializeAnyToJson : 'a -> t
(** [anyToJson value] turns any [value] into a JSON object

This will throw an error if there are any functions anywhere in the value.
*)


val unserializeAnyFromJsonUnsafe : t -> 'a
(** [unsafeAnyFromJson json] converts a serialized JSON object back into the bucklescript runtime value.

Warning: marshaling is currently not type-safe. The type of marshaled data is not transmitted along
the value of the data, making it impossible to check that the data read back possesses the type expected
by the context. The return type of this function is given as 'a, but this is misleading: the returned
OCaml value does not possess type 'a for all 'a; it has one, unique type which cannot be determined at
compile-time.

The programmer should explicitly give the expected type of the returned value, using the following syntax:

(Js.Json.unserializeAnyFromJsonUnsafe json : type)

Anything can happen at run-time if the object does not correspond to the assumed type.
*)
57 changes: 57 additions & 0 deletions jscomp/test/js_json_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ var Block = require("../../lib/js/block.js");
var Js_json = require("../../lib/js/js_json.js");
var Caml_array = require("../../lib/js/caml_array.js");
var Js_primitive = require("../../lib/js/js_primitive.js");
var Belt_MapString = require("../../lib/js/belt_MapString.js");
var Caml_builtin_exceptions = require("../../lib/js/caml_builtin_exceptions.js");

var suites = /* record */[/* contents : [] */0];
Expand Down Expand Up @@ -585,6 +586,61 @@ eq("File \"js_json_test.ml\", line 387, characters 5-12", Js_json.decodeNull({ }

eq("File \"js_json_test.ml\", line 389, characters 5-12", Js_json.decodeNull(1.23), undefined);

function check(loc, value) {
return eq(loc, Js_json.unserializeAnyFromJsonUnsafe(Js_json.serializeAnyToJson(value)), value);
}

check("File \"js_json_test.ml\", line 398, characters 8-15", /* record */[
/* a */2,
/* b */undefined,
/* c : Ok */Block.__(0, ["folks"]),
/* d : [] */0
]);

check("File \"js_json_test.ml\", line 399, characters 8-15", /* :: */[
/* tuple */[
2,
3,
4,
5
],
/* [] */0
]);

check("File \"js_json_test.ml\", line 400, characters 8-15", /* `Poly */[
892709484,
3
]);

check("File \"js_json_test.ml\", line 401, characters 8-15", Belt_MapString.set(Belt_MapString.set(Belt_MapString.empty, "hello", /* :: */[
2,
/* :: */[
3,
/* :: */[
4,
/* [] */0
]
]
]), "folks", /* :: */[
100,
/* [] */0
]));

check("File \"js_json_test.ml\", line 402, characters 8-15", /* array */[
/* `A */[
65,
3
],
/* `B */[
66,
2.0
],
/* `C */[
67,
"hi"
]
]);

Mt.from_pair_suites("js_json_test.ml", suites[0]);

exports.suites = suites;
Expand All @@ -594,4 +650,5 @@ exports.false_ = false_;
exports.true_ = true_;
exports.option_get = option_get;
exports.eq_at_i = eq_at_i;
exports.check = check;
/* v Not a pure module */
12 changes: 12 additions & 0 deletions jscomp/test/js_json_test.ml
Original file line number Diff line number Diff line change
Expand Up @@ -389,4 +389,16 @@ let () =
eq __LOC__
(Js.Json.decodeNull (Js.Json.number 1.23)) None

(* serializeAny tests *)
type aRecord = {a: int; b: float option; c: (string, string) Belt.Result.t; d: aRecord list}

let check loc value = eq loc (Js.Json.unserializeAnyFromJsonUnsafe (Js.Json.serializeAnyToJson value)) value

let () =
check __LOC__ {a=2; b=None; c=Ok "folks"; d=[]};
check __LOC__ [2,3,4,5];
check __LOC__ (`Poly 3);
check __LOC__ (Belt.Map.String.empty |. Belt.Map.String.set "hello" [2;3;4] |. Belt.Map.String.set "folks" [100]);
check __LOC__ [|`A 3; `B 2.0; `C "hi"|]

let () = Mt.from_pair_suites __FILE__ !suites
82 changes: 82 additions & 0 deletions lib/js/js_json.js
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,86 @@ function decodeNull(json) {

}

var _toJson = function (data){
if (!data) {
return data
} else if (typeof data === 'function') {
throw new Error("Cannot serialize a function")
} else if (Array.isArray(data)) {
if (data.tag != null) {
return {
$$tag: data.tag,
$$contents: data.map(_toJson),
$$bsVariant: data[Symbol.for('BsVariant')],
}
} else if (data[Symbol.for('BsVariant')] != null) {
return {
$$bsVariant: data[Symbol.for('BsVariant')],
$$contents: data.map(_toJson)
}
} else if (data[Symbol.for('BsLocalModule')] != null) {
return {
$$bsLocalModule: data[Symbol.for('BsLocalModule')],
$$contents: data.map(_toJson)
}
} else if (data[Symbol.for('BsPolyVar')] != null) {
return {
$$bsPolyVar: data[Symbol.for('BsPolyVar')],
$$contents: data.map(_toJson)
}
} else if (data[Symbol.for('BsRecord')] != null) {
return {
$$bsRecord: data[Symbol.for('BsRecord')],
$$contents: data.map(_toJson)
}
} else {
return data.map(_toJson)
}
} else if (typeof data == 'object') {
var result = {}
Object.keys(data).forEach(key => result[key] = _toJson(data[key]))
return result
} else {
return data
}
};

var serializeAnyToJson = _toJson;

var unserializeAnyFromJsonUnsafe = function (data){
if (!data) {
return data
} else if (typeof data == 'object') {
if (Array.isArray(data)) {
return data.map(unserializeAnyFromJsonUnsafe)
} else if (data.$$contents) {
var result = data.$$contents.map(unserializeAnyFromJsonUnsafe)
if (data.$$tag != null) {
result.tag = data.$$tag
}
if (data.$$bsRecord) {
result[Symbol.for('BsRecord')] = data.$$bsRecord
}
if (data.$$bsPolyVar) {
result[Symbol.for('BsPolyVar')] = data.$$bsPolyVar
}
if (data.$$bsVariant) {
result[Symbol.for('BsVariant')] = data.$$bsVariant
}
if (data.$$bsLocalModule) {
result[Symbol.for('BsLocalModule')] = data.$$bsLocalModule
}
return result
} else {
var result = {}
Object.keys(data).forEach(key => result[key] = unserializeAnyFromJsonUnsafe(data[key]))
return result
}
} else {
return data
}
};

exports.classify = classify;
exports.test = test;
exports.decodeString = decodeString;
Expand All @@ -96,4 +176,6 @@ exports.decodeObject = decodeObject;
exports.decodeArray = decodeArray;
exports.decodeBoolean = decodeBoolean;
exports.decodeNull = decodeNull;
exports.serializeAnyToJson = serializeAnyToJson;
exports.unserializeAnyFromJsonUnsafe = unserializeAnyFromJsonUnsafe;
/* No side effect */