-
-
Notifications
You must be signed in to change notification settings - Fork 366
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 handling of falsy value with the json
option
#223
Conversation
currently, falsey json values will get mistaken for an empty json value -- this makes the comparison more specific, and will match any json value other than `undefined`
Would you be able to add a test too? So we can be sure to not regress it in the future. https://github.com/sindresorhus/ky/blob/master/test/main.js |
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.
If this is to be an officially supported use case, I think the documentation for the json
option should be updated to clarify that any valid JSON serializable value is supported. Currently it implies that only objects are supported.
Both readme.md
and index.d.ts
should be updated.
I've changed the implementation just a bit -- now, setting I also updated docs to reflect this (set their types to Thanks for the feedback! |
index.d.ts
Outdated
*/ | ||
json?: unknown; | ||
json?: any; |
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 should be correctly typed. The keys are strings and the values can be: string, number, boolean, null, object, array.
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.
I'm not sure what you mean by "the keys are strings". I've updated the type to explicitly list those types, and included undefined
, which is technically allowed (though it serializes to null
...)
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.
For TypeScript purposes, I think it should probably stay as unknown
.
https://stackoverflow.com/questions/51439843/unknown-vs-any/51439876
index.d.ts
Outdated
*/ | ||
json?: unknown; | ||
json?: object | Array | string | number | boolean | null | undefined; |
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.
undefined is unnecessary here
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.
that's fine... but what's your reasoning? undefined
is a valid value to pass the json prop here... that said, this is my first time working with ts so I'm not aware of the subtleties...
index.js
Outdated
@@ -242,7 +242,7 @@ class Ky { | |||
this.request = new globals.Request(new globals.Request(url, this.request), this._options); | |||
} | |||
|
|||
if (this._options.json !== undefined) { | |||
if (this._options.hasOwnProperty('json')) { |
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 will make it hard to unset the json
option. Also, it will try to stringify and send undefined
which will cause JSON.parse()
on the server to throw an error.
I think !== undefined
is the way to go.
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.
to unset the prop, you'd delete
it... the tricky thing here is that setting json
is (described as) a "shortcut" for setting .body = JSON.stringify(...)
, so presumably anything you can pass to JSON.stringify
should be able to be passed to json
! and, undefined
will be stringified to null
so the server will be required to parse that (which should be fine).
Having the json
prop act ever so slightly different than how it's described is IMHO a recipe for weird edge cases...
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.
undefined
will be stringified tonull
This is incorrect. JSON.stringify(undefined)
returns undefined
, which JSON.parse()
fails to parse.
readme.md
Outdated
@@ -139,9 +139,9 @@ Internally, the standard methods (`GET`, `POST`, `PUT`, `PATCH`, `HEAD` and `DEL | |||
|
|||
##### json | |||
|
|||
Type: `object` | |||
Type: `any` |
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.
Maybe instead of an actual type (which would be very verbose), we should just say "Any valid JSON value", linking to a reference on MDN or elsewhere that specifies what that is.
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 where I can't contribute, as I have no ts experience. I'm happy to go with whatever y'all decide...
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.
I was speaking only of the documentation for the API in the readme, my comment isn't TypeScript related. The TypeScript definitions in index.d.ts
, on the other hand, obviously need to be valid TypeScript.
the build is now failing on the test |
index.d.ts
Outdated
*/ | ||
json?: unknown; | ||
json?: object | Array | string | number | boolean | null; |
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.
I am going to undo this change because it is unrelated to supporting non-object JSON values and there is some history behind why the TypeScript type is unknown
. @sindresorhus made this change and explained it in #133 (comment). As far as I know, there still isn't a great solution for that
We can talk about it in the comments here in this PR or you can open a new issue if you want the TypeScript types to be stricter. For now, I think unknown
is sufficient to support the changes in this PR.
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.
I'm 👎 on undoing this. I think Sindre was talking about response, not request. When it comes to parsing response, then yes, you have to cast it on your own because you don't know if it's a string, a number or whatever (one of the types mentioned later in this comment). On the other hand, we don't care about the JSON body as long as it is valid JSON. See, it's valid only when it is string | number | boolean | null | {[key: string]: ...} | Array<...>
.
@sindresorhus Can you clarify please?
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.
Apologies, I linked to the wrong comment. I edited and fixed the link.
This just made the option harder to use in practice.
JSON.stringify()
acceptsunknown
too. And it didn't account for when your have custom types with atoJSON()
method. So all in all, the strict type had very little value.
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.
Apologies, I linked to the wrong comment. I edited and fixed the link.
No problem.
And it didn't account for when your have custom types with a toJSON() method.
Well, what about this:
interface JSONable extends Object {
toJSON(): string;
}
(not sure if it works, not at home atm)
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.
unknown
is the only pragmatic type here. I've tried making it strict before and it just turned out to be an inconvenience to users without much value.
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.
LGTM as-is, but we can wait for Sindre's response in regards to the JSONAble
interface (I think it's a good idea if it works as expected).
json
option
Thanks for fixing this 👌 |
I can open a new ticket for this discussion if you'd rather, but, I'm not completely comfortable with the way this was finally resolved. With the current fix, given
there is a functional difference between:
...which will send the body
...which will send no body at all. I know this seems like a little thing but, especially with low-level libraries like this, small deviations are surprising and frustrating. Is there a reason not to use |
I am happy to discuss it further. Let's continue here for now.
No, there isn't. You have made some faulty assumptions. You seem to believe that
Yes. As I mentioned here, this would make it hard to unset the const myOptions = {
json : { time : Date.now() }
};
const myKy1 = ky.extend(myOptions);
myKy1.post('/ping'); // sends json
delete myOptions.json; // does not affect myKy1, as options are already set
const myKy2 = myKy1.extend(myOptions);
myKy2.post('/ping'); // bug: still sends json, even though we intended not to The above code is not "fixed" by using const myOptions = {
json : { time : Date.now() }
};
const myKy1 = ky.extend(myOptions);
myKy1.post('/ping'); // sends json
myOptions.json = undefined; // does not affect myKy1, as options are already set
const myKy2 = myKy1.extend(myOptions);
myKy2.post('/ping'); // does not send json (yay!)
This has the same problems as |
thanks for the followup...
this is correct, and I was wrong, I extrapolated incorrectly from |
Currently, falsey json values will get mistaken for an empty json value -- this makes the comparison more specific, and will match any json value other than
undefined
Fixes #222