Flow: Adding json type

Created on 7 Sep 2017  路  9Comments  路  Source: facebook/flow

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.

feature request

Most helpful comment

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!

Try

All 9 comments

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);

Try Flow

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!

Try

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]

Try

@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.

Was this page helpful?
0 / 5 - 0 ratings