-
-
Notifications
You must be signed in to change notification settings - Fork 98
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: deserialize form data objects #437
feat: deserialize form data objects #437
Conversation
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
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.
Thanks for the PR.
-
Can you have any reference on the implementation syntax for
array
andjson
key structure?
For example, I never seefoo{}
key as an indicator forJSON
value. -
How would you distinguish if
foo[0]
means to bearray
but notobject
property?
For example,qs
did a great job by providing option for different behavior. So, the user is opt-in to which fit them most. -
The current implementation is exposed to
prototype poisoning
attack. -
You would need to write the document for new feature before it get merged.
Hi, @climba03003. I've just updated the PR with the requested changes. In order to keep backward compatibility, I disabled key special notation parsing by default. To enable it, I introduced an option called Answering your previous questions:
|
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.
Nice job, I must admit I can't follow entirely what's going on here but apart from a couple of inline comments I can't spot anything else that would need changing.
@@ -83,6 +93,106 @@ function attachToBody (options, req, reply, next) { | |||
}) | |||
} | |||
|
|||
function validateSerializedKey (tokens, 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.
hard to follow what's going on here but I guess this is something that we have to do. I have a feeling that this code may be coming from somewhere else, possibly slightly modified though. if that is the case, don't forget to cite the source 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.
Even though I originally wanted to save time by starting from some pre-existing battle-tested code, I couldn't find anything close to what I needed, so I ended up writing the entire feature from scratch. Zero third-party code. 😄
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.
@Uzlopak Do you have any advice on the implementation (performance wise)?
index.js
Outdated
if (token === ']' && prevToken === '[') { | ||
parentRef = lastSegment.parentRef[lastSegment.key] ?? (parseArrays ? [] : {}) | ||
key = parseArrays ? parentRef.length : Object.keys(parentRef).length | ||
} else if (parseArrays && token === ']' && /^\d+$/.test(prevToken)) { |
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.
prevToken is a single character.
It is necessary to use regexp for this check?
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.
In this case, prevToken
not necessarily will have a single character. This regex would also match a multi-digit number. But thanks to your comment, I remembered that I was not checking for leading zeroes, so I extracted this checking to a function and explicitly checked if the number has no leading zero.
I've also benchmarked this regex-based checking against two other alternatives, one iteration-based and other that relies on parseFloat. The regex version won by a slim margin.
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.
Unless I read the code wrongly const prevToken = tokens[i - 1]
.
prevToken
is never reassigned and is always a single character.
That means matching multi-digit number is useless for prevToken
.
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.
tokens
is not a string, it's an array of strings. So, prevToken
might be either [
, ]
, {}
, a multi-digit numeric index or an alphanumeric object key.
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.
Then it doesn't make sense why doesn't use a single for-loop.
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 logic I've proposed relies on more than a loop in order to separate concerns. Mixing all steps in a single loop would make it way harder to read, debug and maintain.
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.
Had some thouhts on the code.
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.
Had some thouhts on the code.
@@ -73,7 +73,23 @@ function attachToBody (options, req, reply, next) { | |||
mp.destroy(new Error(`${key} is not allowed as field name`)) | |||
return | |||
} | |||
if (body[key] === undefined) { | |||
|
|||
const tokens = ( |
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.
attachToBody is called only in the preValidation hook in line 277. We could actually add above the addHook a line to normalize the value. Then we dont need to check here for multiple values.
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.
Didn't get it. Could you explain it in more details?
// when the {} special ending is supplied, the value must be valid JSON | ||
if (isValid && tokens[tokens.length - 1] === '{}') { | ||
try { | ||
secureJSON.parse(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.
Your examples are form.append('foo{}', '{"bar":[1,2,3]}')
and more complex keys. But what if the value is a primitive, like 'a' or worse 'null'?
secureParse would not fail in case of 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've included an explicit check for null
, but secureJSON.parse
would, in fact, allow primitives to be passed. Is there a good reason for explicitly preventing this?
|
||
function getSerializedKeySegments (options, body, tokens, value) { | ||
const segments = [{ parentRef: body, key: tokens[0] }] | ||
const parseArrays = options.enableSpecialNotationKeys !== 'objectsOnly' |
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 if enableSpecialNotationKeys is true? Maybe we should normalize like described in a different comment.
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.
At this point, enableSpecialNotationKeys
can only be true
or "objectsOnly"
, as we're explicitly checking it on line 77. So, if it isn't "objectsOnly"
, it necessarily must be true
.
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.
options.enableSpecialNotationKeys !== 'objectOnly' would be true if enableSpecialNotationKeys is true. So parseArrays would be true. So you would generate Arrays and not Objects with numeric keys?!
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.
Exactly. When enableSpecialNotationKeys
is true
, numeric keys generate arrays. When enableSpecialNotationKeys
is objectsOnly
, everything is parsed as an object, so no arrays at all (unless you use the {}
special ending and declare an array using JSON).
@Uzlopak @climba03003 hey there! 👋🏼 I wanted to check in on the status of this PR. Could you please let me know if there's anything else I need to address or if there's any further feedback? Thanks. |
@alcidesqueiroz thanks for your contribution here, and apologies for the lack of feedback. I think this was a valuable contribution and it's a pity that we couldn't integrate it in the codebase. |
@simoneb no worries, mate! 🙂 🙏 |
closes #429
Checklist
npm run test
andnpm run benchmark
and the Code of conduct