Would like to suggest a json type to distinguish JSON Objects from function object.
In case I want to tell in a function parameter that I expect some JSON object, without need to define the object structure, but I don't want to get function as well.
so lets see this code:
/* @flow */
function foo(x: Object): string {
if (x) {
return "a";
}
return "default string";
}
foo(() => {})
this will throw no errors.
So I would like to suggest to have a json type , something like this
/* @flow */
function foo(x: json): string {
if (x) {
return "a";
}
return "default string";
}
foo(() => {}) // throw an error.
foo({a:1}) // no error
We can keep the general Object to allow both, but to give better declarations of objects.
You can do this yourself (Try Flow):
type ObjNotFn = { $call?: empty }
function foo(x: ObjNotFn): string {
if (x) {
return "a";
}
return "default string";
}
foo(() => {}) // throws an error.
foo({a:1}) // no error
That is true but not 100% safe. I mean this will not work Try Flow:
type ObjNotFn = { $call?: empty }
function foo(x: ObjNotFn): string {
if (x) {
return "a";
}
return "default string";
}
foo({$call:5}) // also throw an error
foo(() => {}) // throws an error.
So while this will work for most of the code, I would still prefer to use some inner type json that might relay on type check like:
const toType = (obj) => ({}).toString.call(obj).match(/\s([a-zA-Z]+)/)[1].toLowerCase();
toType({$call:5}) // "object"
toType(() => {}) // "function"
Unfortunately Flow decided to use $call as the way to indicate that some object is callable, so it's already not safe:
const objNotFn = {};
objNotFn.$call = ()=>{};
objNotFn(); // no error. wat?
still not entirely safe as pointed out - though I think this is actually related to this #946 - but I use this for JSON for pretty good effect:
type PrimitiveType = null | string | boolean | number | Date;
type JSONType = ({ [key: string]: PrimitiveType | JSONType | void } & { $call?: void }) | PrimitiveType;
does not protect against circular references, however
Date is not a primitive type, nor can an instance of it exist in parsed JSON.
I've been using this to check whether something can be safely serialized to JSON:
type JSON =
| null
| void
| string
| number
| boolean
| { [string]: JSON }
| Array<JSON>;
const object = {
null: null,
void: undefined,
string: '',
number: 0,
boolean: false,
object: {},
array: [],
};
const array = [null, undefined, '', 0, false, {}, []];
const complexObject = { ...object, array, object };
const complexArray = [...array, array, object];
(null: JSON);
(undefined: JSON);
('': JSON);
(0: JSON);
(false: JSON);
({}: JSON);
([]: JSON);
(object: JSON);
(array: JSON);
(complexObject: JSON);
(complexArray: JSON);
It seems like $Shape can help here:
// @flow
export type Json =
| JsonPrimitive
| JsonArray
| JsonObject
;
export type JsonPrimitive =
| void
| null
| string
| number
| boolean
;
export type JsonArray = Array<Json>;
export type JsonObject = $Shape<{ [string]: Json }>;
const json1: Json = { foo: 123 }; // OK
const json2: Json = { $call: 123 }; // OK
const json3: Json = () => {}; // Error!
I would support having this as a native part of Flow. The work around posted here don't seem to handle nested objects well when matching against other flow types:
// @flow
export type Json = JsonPrimitive | JsonArray | JsonObject;
export type JsonPrimitive = void | null | string | number | boolean;
export type JsonArray = Array<Json>;
export type JsonObject = $Shape<{ [string]: Json }>;
export type OtherType = {
strings: Array<string>,
};
const json1 : Json = { foo: 123 }; // OK
const json2: Json = { $call: 123 }; // OK
const json3: Json = () => {}; // Error
const json4: Json = {
strings: ['asdf', 'qwer'],
}; // OK
const json5: OtherType = {
strings: ['asdf', 'qwer'],
};
const json6: Json = json5; // Expected to work
const json6: Json = json5; // Expected to work
^ Cannot assign `json5` to `json6` because string [1] is incompatible with undefined [2] in array element of property `strings`.
References:
14: strings: Array<string>,
^ [1]
7: export type JsonArray = Array<Json>;
^ [2]
@lewchuk The reason that doesn't work like you might think is if you did json6.strings.push(undefined) (a perfectly valid thing to do on a JsonArray), the object would no longer be a valid OtherType. Flow is just recognizing that possibility.
Most helpful comment
It seems like
$Shapecan help here:Try