I've been messing around with typing a "camel caser" function (one that consumes JSON and camel cases its keys). I've run into a few issues along the way, and I'm curious if y'all have any suggestions.
A camel caser never changes the shape of its argument, so I would like to preserve the type of whatever I pass in; ideally, calling camelize
on an array of numbers would return another array of numbers, etc.
I've started with the following:
type JSON = null | string | number | boolean | { [string]: JSON } | JSON[]
function camelize<J: JSON>(json: J): J {
throw "just typecheck please"
}
This works great for the simple cases, null
, string
, number
, and boolean
, but things don't quite work perfectly for JSON dictionaries or array. For example:
const dictionary: { [string]: number } = { key: 123 }
const camelizedDictionary = camelize(dictionary)
will fail with a type error. A similar issue will come up if you pass in a value of, say, type number[]
. I think I understand the issue: arrays and dictionaries are mutable, and hence invariant in the type of the values they point to; an array of numbers is not a subtype of JSON[]
, so Flow complains. If arrays and dictionaries were covariant though, I believe this approach would work.
Given that they're not covariant though, do y'all have any suggestions for how I should think about this?
Use property variance to solve your problem with dictionaries:
type JSON = null | string | number | boolean | { +[string]: JSON } | JSON[]
https://flowtype.org/blog/2016/10/04/Property-Variance.html
As for your problem with Arrays
, as you've pointed out the issue is with mutability. Unfortunately Array<number>
is not a subtype of Array<JSON>
. I think the only way to get what you want is to explicitly enumerate all of the allowed Array
types:
type JSON = null | string | number | boolean | { +[string]: JSON } | Array<JSON> | Array<number>;
I've added just Array<number>
here to make my point. Obviously this is burdensome, especially if you also want to include arbitrary mixes of JSON elements (like Array<string | number | null>
). But it will hopefully address the common issues.
(I've also changed it to the array syntax with which I am more familiar but there should be no difference in functionality).
There has been talk of adding a read-only version of Array
which would be analogous to covariant object types, and I believe it would solve your problem here. But so far nothing has really come of it.
Complete tryflow based on the one you gave.