Skip to content
Snippets Groups Projects
Commit 741a8e74 authored by Loïc Correnson's avatar Loïc Correnson
Browse files

[dome] JSON decoders

parent cda596fa
No related branches found
No related tags found
No related merge requests found
......@@ -5,7 +5,7 @@
import { DEVEL } from 'dome/system';
/**
Safe JSON utilities
Safe JSON utilities.
@package dome/data/json
*/
......@@ -32,16 +32,16 @@ export function parse(text: string, noError = false): json {
}
/**
Export JSON as a compact string.
Export JSON (or any data) as a compact string.
*/
export function stringify(js: json) {
export function stringify(js: any) {
return JSON.stringify(js, undefined, 0);
}
/**
Export JSON as a string with indented content.
Export JSON (or any data) as a string with indented content.
*/
export function pretty(js: json) {
export function pretty(js: any) {
return JSON.stringify(js, undefined, 2);
}
......@@ -49,55 +49,105 @@ export function pretty(js: json) {
// --- SAFE Decoder
// --------------------------------------------------------------------------
/** Decoder for values of type `D`. */
export type Safe<D> = (js: json) => D;
/**
Decode for values of type `D`, if any.
Same as `Safe<D | undefined>`.
*/
export type Loose<D> = (js: json) => D | undefined;
export type Strict<D> = (js: json) => D;
// --------------------------------------------------------------------------
// --- Primitives
// --------------------------------------------------------------------------
/** Primitive JSON number or `undefined`. */
export const jNumber: Loose<number> = (js: json) => (
typeof js === 'number' ? js : undefined
);
export const jZero: Strict<number> = (js: json) => (
/** Primitive JSON number or `0`. */
export const jZero: Safe<number> = (js: json) => (
typeof js === 'number' ? js : 0
);
/** Primitive JSON boolean or `undefined`. */
export const jBoolean: Loose<boolean> = (js: json) => (
typeof js === 'boolean' ? js : undefined
);
export const jTrue: Strict<boolean> = (js: json) => (
/** Primitive JSON boolean or `true`. */
export const jTrue: Safe<boolean> = (js: json) => (
typeof js === 'boolean' ? js : true
);
export const jFalse: Strict<boolean> = (js: json) => (
/** Primitive JSON boolean or `false`. */
export const jFalse: Safe<boolean> = (js: json) => (
typeof js === 'boolean' ? js : false
);
/** Primitive JSON string or `undefined`. */
export const jString: Loose<string> = (js: json) => (
typeof js === 'string' ? js : undefined
);
export function jEnum(...values: string[]): Loose<string> {
var m = new Map<string, string>();
/**
One of the enumerated _constants_ or `undefined`.
The typechecker will prevent you from listing values that are not in
type `A`. However, it will not protected you
from missings constants in `A`.
*/
export function jEnum<A>(...values: ((string | number) & A)[]): Loose<A> {
var m = new Map<string | number, A>();
values.forEach(v => m.set(v, v));
return (v: json) => (typeof v === 'string' ? m.get(v) : undefined);
}
/**
Refine a loose decoder with some default value.
The default value is returned when the provided JSON is `undefined` or
when the loose decoder returns `undefined`.
*/
export function jDefault<A>(
fn: Loose<A>,
defaultValue: A,
): Strict<A> {
): Safe<A> {
return (js: json) => js === undefined ? defaultValue : (fn(js) ?? defaultValue);
}
export function jArray<A>(fn: Strict<A>): Strict<A[]> {
/**
Force returning `undefined` or a default value for `undefined` JSON input.
Typically usefull to leverage an existing `Safe<A>` decoder.
*/
export function jOption<A>(fn: Safe<A>, defaultValue?: A): Loose<A> {
return (js: json) => (js === undefined ? defaultValue : fn(js));
}
/**
Force returning `undefined` or a default value for `undefined` _or_ `null`
JSON input. Typically usefull to leverage an existing `Safe<A>` decoder.
*/
export function jNull<A>(fn: Safe<A>, defaultValue?: A): Loose<A> {
return (js: json) => (js === undefined || js === null ? defaultValue : fn(js));
}
/**
Apply the decoder on each item of a JSON array, or return `[]` otherwize.
Can be also applied on a _loose_ decoder, but you will get
an array with possibly `undefined` elements. Use [[jList]]
to discard undefined elements, or use a true « safe » decoder.
*/
export function jArray<A>(fn: Safe<A>): Safe<A[]> {
return (js: json) => Array.isArray(js) ? js.map(fn) : [];
}
export function jList<A>(fn: Loose<A>): Strict<A[]> {
/**
Apply the loose decoder on each item of a JSON array, discarding
all `undefined` elements. To keep all, possibly undefined array entries,
use [[jArray]] instead.
*/
export function jList<A>(fn: Loose<A>): Safe<A[]> {
return (js: json) => {
const buffer: A[] = [];
if (Array.isArray(js)) js.forEach(vj => {
......@@ -108,7 +158,107 @@ export function jList<A>(fn: Loose<A>): Strict<A[]> {
};
}
export
/** Apply a pair of decoders to JSON pairs, or return `undefined`. */
export function jPair<A, B>(
fa: Safe<A>,
fb: Safe<B>,
): Loose<[A, B]> {
return (js: json) => Array.isArray(js) ? [
fa(js[0]),
fb(js[1]),
] : undefined;
}
/** Similar to [[jPair]]. */
export function jTriple<A, B, C>(
fa: Safe<A>,
fb: Safe<B>,
fc: Safe<C>,
): Loose<[A, B, C]> {
return (js: json) => Array.isArray(js) ? [
fa(js[0]),
fb(js[1]),
fc(js[2]),
] : undefined;
}
/** Similar to [[jPair]]. */
export function jTuple4<A, B, C, D>(
fa: Safe<A>,
fb: Safe<B>,
fc: Safe<C>,
fd: Safe<D>,
): Loose<[A, B, C, D]> {
return (js: json) => Array.isArray(js) ? [
fa(js[0]),
fb(js[1]),
fc(js[2]),
fd(js[3]),
] : undefined;
}
/** Similar to [[jPair]]. */
export function jTuple5<A, B, C, D, E>(
fa: Safe<A>,
fb: Safe<B>,
fc: Safe<C>,
fd: Safe<D>,
fe: Safe<E>,
): Loose<[A, B, C, D, E]> {
return (js: json) => Array.isArray(js) ? [
fa(js[0]),
fb(js[1]),
fc(js[2]),
fd(js[3]),
fe(js[4]),
] : undefined;
}
/**
Decoders for each property of object type `A`.
Optional fields in `A` can be assigned a loose decoder.
*/
export type Props<A> = {
[P in keyof A]: Safe<A[P]>;
}
/**
Decode an object given the decoders of its fields.
Returns `undefined` for non-object JSON.
*/
export function jObject<A>(fp: Props<A>): Loose<A> {
return (js: json) => {
if (js !== null && typeof js === 'object' && !Array.isArray(js)) {
const buffer = {} as A;
for (var k of Object.keys(fp)) {
const fn = fp[k as keyof A];
const fv = fn(js[k]);
buffer[k as keyof A] = fv;
}
return buffer;
}
return undefined;
};
}
/** Type of dictionaries. */
export type dict<A> = { [key: string]: A };
/**
Decode a JSON dictionary, dicarding all inconsistent entries.
If the JSON contains no valid entry, still returns `{}`.
*/
export function jDictionary<A>(fn: Loose<A>): Safe<dict<A>> {
return (js: json) => {
const buffer: dict<A> = {};
if (js !== null && typeof js === 'object' && !Array.isArray(js)) {
for (var k of Object.keys(js)) {
const fv = fn(js[k]);
if (fv) buffer[k] = fv;
}
}
return buffer;
};
}
// --------------------------------------------------------------------------
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment