json-rpc: Fork JSON compatibility code from @iov/encoding
This commit is contained in:
parent
5fa013cc92
commit
685fc54066
125
packages/json-rpc/src/compatibility.spec.ts
Normal file
125
packages/json-rpc/src/compatibility.spec.ts
Normal file
@ -0,0 +1,125 @@
|
||||
import { isJsonCompatibleArray, isJsonCompatibleDictionary, isJsonCompatibleValue } from "./compatibility";
|
||||
|
||||
describe("json", () => {
|
||||
function sum(a: number, b: number): number {
|
||||
return a + b;
|
||||
}
|
||||
|
||||
describe("isJsonCompatibleValue", () => {
|
||||
it("returns true for primitive types", () => {
|
||||
expect(isJsonCompatibleValue(null)).toEqual(true);
|
||||
expect(isJsonCompatibleValue(0)).toEqual(true);
|
||||
expect(isJsonCompatibleValue(1)).toEqual(true);
|
||||
expect(isJsonCompatibleValue("abc")).toEqual(true);
|
||||
expect(isJsonCompatibleValue(true)).toEqual(true);
|
||||
expect(isJsonCompatibleValue(false)).toEqual(true);
|
||||
});
|
||||
|
||||
it("returns true for arrays", () => {
|
||||
expect(isJsonCompatibleValue([1, 2, 3])).toEqual(true);
|
||||
expect(isJsonCompatibleValue([1, "2", true, null])).toEqual(true);
|
||||
expect(isJsonCompatibleValue([1, "2", true, null, [1, "2", true, null]])).toEqual(true);
|
||||
expect(isJsonCompatibleValue([{ a: 123 }])).toEqual(true);
|
||||
});
|
||||
|
||||
it("returns true for simple dicts", () => {
|
||||
expect(isJsonCompatibleValue({ a: 123 })).toEqual(true);
|
||||
expect(isJsonCompatibleValue({ a: "abc" })).toEqual(true);
|
||||
expect(isJsonCompatibleValue({ a: true })).toEqual(true);
|
||||
expect(isJsonCompatibleValue({ a: null })).toEqual(true);
|
||||
});
|
||||
|
||||
it("returns true for dict with array", () => {
|
||||
expect(isJsonCompatibleValue({ a: [1, 2, 3] })).toEqual(true);
|
||||
expect(isJsonCompatibleValue({ a: [1, "2", true, null] })).toEqual(true);
|
||||
});
|
||||
|
||||
it("returns true for nested dicts", () => {
|
||||
expect(isJsonCompatibleValue({ a: { b: 123 } })).toEqual(true);
|
||||
});
|
||||
|
||||
it("returns false for functions", () => {
|
||||
expect(isJsonCompatibleValue(sum)).toEqual(false);
|
||||
});
|
||||
|
||||
it("returns true for empty dicts", () => {
|
||||
expect(isJsonCompatibleValue({})).toEqual(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("isJsonCompatibleArray", () => {
|
||||
it("returns false for primitive types", () => {
|
||||
expect(isJsonCompatibleArray(null)).toEqual(false);
|
||||
expect(isJsonCompatibleArray(undefined)).toEqual(false);
|
||||
expect(isJsonCompatibleArray(0)).toEqual(false);
|
||||
expect(isJsonCompatibleArray(1)).toEqual(false);
|
||||
expect(isJsonCompatibleArray("abc")).toEqual(false);
|
||||
expect(isJsonCompatibleArray(true)).toEqual(false);
|
||||
expect(isJsonCompatibleArray(false)).toEqual(false);
|
||||
});
|
||||
|
||||
it("returns true for arrays", () => {
|
||||
expect(isJsonCompatibleArray([1, 2, 3])).toEqual(true);
|
||||
expect(isJsonCompatibleArray([1, "2", true, null])).toEqual(true);
|
||||
expect(isJsonCompatibleArray([1, "2", true, null, [1, "2", true, null]])).toEqual(true);
|
||||
expect(isJsonCompatibleArray([{ a: 123 }])).toEqual(true);
|
||||
});
|
||||
|
||||
it("returns false for dicts", () => {
|
||||
expect(isJsonCompatibleArray({ a: 123 })).toEqual(false);
|
||||
expect(isJsonCompatibleArray({ a: "abc" })).toEqual(false);
|
||||
expect(isJsonCompatibleArray({ a: true })).toEqual(false);
|
||||
expect(isJsonCompatibleArray({ a: null })).toEqual(false);
|
||||
});
|
||||
|
||||
it("returns false for functions", () => {
|
||||
expect(isJsonCompatibleArray(sum)).toEqual(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("isJsonCompatibleDictionary", () => {
|
||||
it("returns false for primitive types", () => {
|
||||
expect(isJsonCompatibleDictionary(null)).toEqual(false);
|
||||
expect(isJsonCompatibleDictionary(undefined)).toEqual(false);
|
||||
expect(isJsonCompatibleDictionary(0)).toEqual(false);
|
||||
expect(isJsonCompatibleDictionary(1)).toEqual(false);
|
||||
expect(isJsonCompatibleDictionary("abc")).toEqual(false);
|
||||
expect(isJsonCompatibleDictionary(true)).toEqual(false);
|
||||
expect(isJsonCompatibleDictionary(false)).toEqual(false);
|
||||
});
|
||||
|
||||
it("returns false for other objects", () => {
|
||||
expect(isJsonCompatibleDictionary(new Uint8Array([0x00]))).toEqual(false);
|
||||
expect(isJsonCompatibleDictionary(/123/)).toEqual(false);
|
||||
expect(isJsonCompatibleDictionary(new Date())).toEqual(false);
|
||||
});
|
||||
|
||||
it("returns false for arrays", () => {
|
||||
expect(isJsonCompatibleDictionary([1, 2, 3])).toEqual(false);
|
||||
});
|
||||
|
||||
it("returns false for functions", () => {
|
||||
expect(isJsonCompatibleDictionary(sum)).toEqual(false);
|
||||
});
|
||||
|
||||
it("returns true for empty dicts", () => {
|
||||
expect(isJsonCompatibleDictionary({})).toEqual(true);
|
||||
});
|
||||
|
||||
it("returns true for simple dicts", () => {
|
||||
expect(isJsonCompatibleDictionary({ a: 123 })).toEqual(true);
|
||||
expect(isJsonCompatibleDictionary({ a: "abc" })).toEqual(true);
|
||||
expect(isJsonCompatibleDictionary({ a: true })).toEqual(true);
|
||||
expect(isJsonCompatibleDictionary({ a: null })).toEqual(true);
|
||||
});
|
||||
|
||||
it("returns true for dict with array", () => {
|
||||
expect(isJsonCompatibleDictionary({ a: [1, 2, 3] })).toEqual(true);
|
||||
expect(isJsonCompatibleDictionary({ a: [1, "2", true, null] })).toEqual(true);
|
||||
});
|
||||
|
||||
it("returns true for nested dicts", () => {
|
||||
expect(isJsonCompatibleDictionary({ a: { b: 123 } })).toEqual(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
73
packages/json-rpc/src/compatibility.ts
Normal file
73
packages/json-rpc/src/compatibility.ts
Normal file
@ -0,0 +1,73 @@
|
||||
/**
|
||||
* A single JSON value. This is the missing return type of JSON.parse().
|
||||
*/
|
||||
export type JsonCompatibleValue =
|
||||
| JsonCompatibleDictionary
|
||||
| JsonCompatibleArray
|
||||
| string
|
||||
| number
|
||||
| boolean
|
||||
| null;
|
||||
|
||||
/**
|
||||
* An array of JsonCompatibleValue
|
||||
*/
|
||||
// Use interface extension instead of type alias to make circular declaration possible.
|
||||
export interface JsonCompatibleArray extends ReadonlyArray<JsonCompatibleValue> {}
|
||||
|
||||
/**
|
||||
* A string to json value dictionary.
|
||||
*/
|
||||
export interface JsonCompatibleDictionary {
|
||||
readonly [key: string]: JsonCompatibleValue | readonly JsonCompatibleValue[];
|
||||
}
|
||||
|
||||
export function isJsonCompatibleValue(value: unknown): value is JsonCompatibleValue {
|
||||
if (
|
||||
typeof value === "string" ||
|
||||
typeof value === "number" ||
|
||||
typeof value === "boolean" ||
|
||||
value === null ||
|
||||
// eslint-disable-next-line @typescript-eslint/no-use-before-define
|
||||
isJsonCompatibleArray(value) ||
|
||||
// eslint-disable-next-line @typescript-eslint/no-use-before-define
|
||||
isJsonCompatibleDictionary(value)
|
||||
) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export function isJsonCompatibleArray(value: unknown): value is JsonCompatibleArray {
|
||||
if (!Array.isArray(value)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const item of value) {
|
||||
if (!isJsonCompatibleValue(item)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// all items okay
|
||||
return true;
|
||||
}
|
||||
|
||||
export function isJsonCompatibleDictionary(data: unknown): data is JsonCompatibleDictionary {
|
||||
if (typeof data !== "object" || data === null) {
|
||||
// data must be a non-null object
|
||||
return false;
|
||||
}
|
||||
|
||||
// Exclude special kind of objects like Array, Date or Uint8Array
|
||||
// Object.prototype.toString() returns a specified value:
|
||||
// http://www.ecma-international.org/ecma-262/7.0/index.html#sec-object.prototype.tostring
|
||||
if (Object.prototype.toString.call(data) !== "[object Object]") {
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO: replace with Object.values when available (ES2017+)
|
||||
const values = Object.getOwnPropertyNames(data).map((key) => (data as any)[key]);
|
||||
return values.every(isJsonCompatibleValue);
|
||||
}
|
||||
23
packages/json-rpc/types/compatibility.d.ts
vendored
Normal file
23
packages/json-rpc/types/compatibility.d.ts
vendored
Normal file
@ -0,0 +1,23 @@
|
||||
/**
|
||||
* A single JSON value. This is the missing return type of JSON.parse().
|
||||
*/
|
||||
export declare type JsonCompatibleValue =
|
||||
| JsonCompatibleDictionary
|
||||
| JsonCompatibleArray
|
||||
| string
|
||||
| number
|
||||
| boolean
|
||||
| null;
|
||||
/**
|
||||
* An array of JsonCompatibleValue
|
||||
*/
|
||||
export interface JsonCompatibleArray extends ReadonlyArray<JsonCompatibleValue> {}
|
||||
/**
|
||||
* A string to json value dictionary.
|
||||
*/
|
||||
export interface JsonCompatibleDictionary {
|
||||
readonly [key: string]: JsonCompatibleValue | readonly JsonCompatibleValue[];
|
||||
}
|
||||
export declare function isJsonCompatibleValue(value: unknown): value is JsonCompatibleValue;
|
||||
export declare function isJsonCompatibleArray(value: unknown): value is JsonCompatibleArray;
|
||||
export declare function isJsonCompatibleDictionary(data: unknown): data is JsonCompatibleDictionary;
|
||||
Loading…
Reference in New Issue
Block a user