-
Notifications
You must be signed in to change notification settings - Fork 0
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
feat: Serialize objects #10
base: main
Are you sure you want to change the base?
Conversation
razor-x
commented
Feb 24, 2025
- docs: Update example in README
- docs: Add Date and Temporal to examples
- Add float to examples
- Remove test.only
- feat: Serialize plain objects to dot path notation
- doc: Note serialization guarantee
- Serialization of objects and nested objects first serializes the keys | ||
to dot-path format and then serializes the values as above, e.g., | ||
`{ foo: 'a', bar: { baz: 'b', fizz: [1, 2] } }` serializes to | ||
`foo=a&bar.baz=b&bar.fizz=1&bar.fizz=2`. |
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.
Did you consider using PHP style here? That is:
?foo=a&bar[baz]=b&bar[fizz][]=1&bar[fizz][]=2
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.
No I did not directly consider it, but thanks for the suggestion.
I chose dot notation because it's the simplest to read, write and parse (and does not need escaping in other contexts like the command line).
The suggested bracket notation would work, but it looks too similar to the competing array notation bar[]=
. Since URLSearchParams
standardizes on foo=a&foo=b
and not bracket notation, I decided not to introduce brackets.
Note that we still need to write a standardized parser, and the parser can be more permissive with what it accepts. E.g., the serialize will set the base standard, but our parser can still choose to accept other formats within reason, and if they do not conflict with the serializer.
For example, we can unambiguously parse foo.bar=1
and foo[bar]=1
to { foo: { bar: 1 } }
since the Seam API will not allow brackets or dots in param names.
t.is( | ||
serializeUrlSearchParams({ | ||
foo: 1, | ||
bar: { baz: [1, 'a'] }, |
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.
what does an array with a single element serialize to?
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.
There is a test above, and info is in the README
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.
it was a leading question 😁 - this means we're losing type information - we're relying on our consumer knowing that foo
is an array, whereas if we used the more explicit foo[]=1
we'll produce an array regardless of the cardinality
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 covered in the README addition in this PR
Serialization is guaranteed to be well-defined within each type, i.e., if the type of value for a given key in the query string is fixed and known by the consumer parsing the string, it can be unambigously parsed back to the original primitive value.
The scope of this serializer is only in the context of supporting the Seam API. It does not need to preserve type information because the type information will be contained in the parsing step. The Seam API does not have "ambiguous" parameters, e.g., it does not allow for string | string[]
or number | string
so losing this type information is not a concern and can be moved out of scope.
Converting a JS object to a url will always be lossy unless explicitly encoded as JSON, so this library makes tradeoffs favoring readability and usability over less-lossy serializations, because it does not need to preserve the type information due to the constraints in the allowed schema domain.
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.
@phpnode I am still trying to decide the best way to handle nullable
params though. Currently null
and undefined
are both removed by the serializer, but a GET query for foo=null
, i.e. "filter for when foo is null" vs. omitting foo
, i.e. "no filter on foo" is a more common use case.
We could serialize { foo: null }
to foo=null
, but then the API cannot handle the literal string 'null'
vs the value null
. This is only an issue for nullable strings. Any other nullable param type (number, array, etc.) can handle foo=null
as null
would otherwise be an invalid 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.
@phpnode I'm working on the parser scope here: seamapi/url-search-params-parser#1
For null
, I'm leaning towards serializing it to null
in all cases and allowing the parser to decide when to treat null
as a string or a null
. I don't think there are many valid cases where null
would be a useful string value in a GET request.
This could be an option, e.g., serialzieNullToString
, but I don't know when we would use a different value. The serializer should just make a decision on this.
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 would use an empty string to represent null - i think there's even fewer cases where we'd want to differentiate between an empty string and null, so you end up with ?foo=&bar=123
which seems fine. It's also how an empty form field would be serialized by the browser