-
Notifications
You must be signed in to change notification settings - Fork 5
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix typing of json
runtype output
#73
Conversation
json
runtype type inferencejson
runtype output
// basic | ||
{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added these. Not sure if there should be more / if there are other tests that would be preferred.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good to me.
// record with json | ||
{ | ||
const rt = st.record({ | ||
a: st.string(), | ||
b: st.json( | ||
st.record({ | ||
c: st.string(), | ||
}), | ||
), | ||
d: st.string(), | ||
}) | ||
|
||
expectType<{ a: string; b: { c: string }; d: string }>(rt(data)) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because the tests in test-d/json.test-d.ts
check that json
produces the correct types, this test is not strictly necessary, so I can remove if that is preferred. I also know it's impractical to have tests for all the possible interactions between runtypes. However, I added this one as it is used in my use case (e.g. a record
that contains a json
, which then contains a record
), but I can remove if that is preferred.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perfect, I completely agree here.
It is not possible to model all interactions but having some tested interactions, especially based on previous bugs is really useful and adds documentation and robustness to the tests.
export function json<T>(rt: Runtype<T>): Runtype<T> { | ||
return internalRuntype<any>((v, failOrThrow) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the change made to the json
runtype.
We want json
to return a Runtype
which is the same Runtype
as the one passed to json
. (And this runtype will be applied to the string given to it after the string has been parsed into JSON).
The current change allows for that: the type of the param of json
is the same as its return type, and the any
generic param given to internalRuntype
means that internalRuntype
returns a Runtype<any>
which can be assigned to Runtype<T>
.
I've seen this kind of use of any
with internalRuntype
in, for example, array
and tuple
. However, I'm not sure if there is a better way of achieving this without using any
, or without explicitly specifying internalRuntype
's generic param.
I'm no TS expert, and I'm not sure of all the consequences of this change, so please let me know if this is incorrect 🙂
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The way you did is is 100% correct within simple-runtypes.
The reason you need to use any
is that I was being lazy with building the "internal runtype" stuff. There are Runtype<T>
s and InternalRuntype<T>
s. The internal ones have that additional parameter so that they do not throw an error but instead return an error object. This was all done to stay fast (catching errors and using many nested try-catch statements is slow as hell).
But the internalRuntype
constructor does return a Runtype
,not an InternalRuntype
. And also expects a Runtype
to be passed through not an InternalRuntype
. Internal and external runtypes and constructors should be properly separated and a simple casting type should turn internal into external runtype (that was the idea behind the failOrThrow
flag - to not wrap every runtype in another function).
Anyways, as much as I love having everything typed, IMHO it is okay to sometimes have some untyped internals.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thx!
// record with json | ||
{ | ||
const rt = st.record({ | ||
a: st.string(), | ||
b: st.json( | ||
st.record({ | ||
c: st.string(), | ||
}), | ||
), | ||
d: st.string(), | ||
}) | ||
|
||
expectType<{ a: string; b: { c: string }; d: string }>(rt(data)) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perfect, I completely agree here.
It is not possible to model all interactions but having some tested interactions, especially based on previous bugs is really useful and adds documentation and robustness to the tests.
export function json<T>(rt: Runtype<T>): Runtype<T> { | ||
return internalRuntype<any>((v, failOrThrow) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The way you did is is 100% correct within simple-runtypes.
The reason you need to use any
is that I was being lazy with building the "internal runtype" stuff. There are Runtype<T>
s and InternalRuntype<T>
s. The internal ones have that additional parameter so that they do not throw an error but instead return an error object. This was all done to stay fast (catching errors and using many nested try-catch statements is slow as hell).
But the internalRuntype
constructor does return a Runtype
,not an InternalRuntype
. And also expects a Runtype
to be passed through not an InternalRuntype
. Internal and external runtypes and constructors should be properly separated and a simple casting type should turn internal into external runtype (that was the idea behind the failOrThrow
flag - to not wrap every runtype in another function).
Anyways, as much as I love having everything typed, IMHO it is okay to sometimes have some untyped internals.
// basic | ||
{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good to me.
Reproduction of issue: codesandbox.io
While using the
json
runtype for the first time lately, I noticed that, while the runtype was working in terms of producing the correct output, it was losing type information. For example, after adding the tests from this PR, but before changing anything insrc/json.ts
, if I ranyarn test:types
I got the following output:output
And in VS Code I can see the following in the
json
tests that already exist intest/json.test.ts
:screenshot
After making this PR's changes in
src/json.ts
, the tests pass and I see the following in VS Code:screenshot