-
Notifications
You must be signed in to change notification settings - Fork 11.1k
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
Support ndjson for application/json requests #4969
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -161,11 +161,56 @@ import { getLicense } from '@/License'; | |
import { corsMiddleware } from './middlewares/cors'; | ||
|
||
require('body-parser-xml')(bodyParser); | ||
const typeis = require('type-is'); | ||
|
||
const exec = promisify(callbackExec); | ||
|
||
export const externalHooks: IExternalHooksClass = ExternalHooks(); | ||
|
||
function parseNdjson(str: string): any[] { | ||
const result: any[] = []; | ||
const items = str | ||
.split('\n') | ||
.map((item: string) => item.trim()) | ||
.filter((item: string) => item !== ''); | ||
for (const item of items) { | ||
try { | ||
result.push(JSON.parse(item)); | ||
} catch (e) { | ||
throw Error( | ||
// eslint-disable-next-line prettier/prettier | ||
`error parsing ndjson, while parsing item ${result.length + 1} of ${result.length}: ${e.message}`, | ||
{ cause: e }, | ||
); | ||
} | ||
} | ||
return result; | ||
} | ||
|
||
function onJsonErrorTryNdjson() { | ||
return (err: any, req: express.Request, res: express.Response, next: express.NextFunction) => { | ||
if ( | ||
typeis(req, ['application/json', 'application/x-ndjson']) && | ||
err.type === 'entity.parse.failed' | ||
) { | ||
let body = err.body; | ||
if (body.match(/^\s*\{[^\n]*}\s*(\n\s*\{[^\n]*}\s*)+$/)) { | ||
try { | ||
const result = parseNdjson(body); | ||
req.body = result; | ||
req.rawBody = Buffer.from(`[${result.join(',\n')}]`); | ||
res.status(200); | ||
next(); | ||
return; //this is the only "happy flow" - in every other case we continue with the original error | ||
} catch (e) { | ||
console.error(e); | ||
} | ||
} | ||
} | ||
next(err); | ||
}; | ||
} | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
This code is equivalent to the original code, but it uses the match method to check if the body string matches the regular expression, rather than using the match method in a boolean context. This can make the code easier to read and understand. Additionally, the return statement at the end of the if block is moved to the end of the block, to improve readability and to avoid having to use a comment to indicate the end of the "happy flow" code path. Finally, the variable match is introduced to store the result of the match method, so that it can be used in the if statement. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. is this ChatGPT?
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What do you mean? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Well, the changes made to the code were intended to improve its readability and maintainability, as when I looked at it at first, I wasn't sure that's the best option to go about it. You are basically using the match method in a boolean context, which makes the whole thing not clear (at least to me), and not obvious what is being checked for. I used the match method to check if the body string matches the rregex, and I think this is more explicit and straightforward. I hope that explains things. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Lol, but appreciate the doubt :) |
||
class App { | ||
app: express.Application; | ||
|
||
|
@@ -666,6 +711,9 @@ class App { | |
}), | ||
); | ||
|
||
// Support ndjson, by converting the ndjson to an array of objects (only if the body is indeed ndjson, with more than one line) | ||
this.app.use(onJsonErrorTryNdjson()); | ||
|
||
// Support application/xml type post data | ||
this.app.use( | ||
// @ts-ignore | ||
|
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
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.
version of the code is equivalent to the original code, but it uses the map method to transform the array of strings into an array of parsed JSON objects, rather than using a for loop. This can make the code easier to read and understand. Additionally, the trim method is used to remove any leading or trailing whitespace from the input string, and the filter method is removed since it is not necessary. Finally, the error message is modified to correctly reference the current item being parsed in the map callback.
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!
The code looks better indeed, more readable and there's less memory allocation - trimming each line allocates a new string.
About removing the filter -
an empty line is acceptable according to the spec:
From my experience, API providers WILL do stupid things like putting empty-lines in the middle of an ndjson, occasionally or all the time (reminder - I opened this PR because of an API provider that did not adhere to standard).
It would be a shame that the n8n user get stuck because of this.
So I think it's better to filter out empty-lines nevertheless.
I wish there was a
str.isEmpty()
that does not allocates a new string.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.
Yes, I agree, it would be fab to have a str.isEmpty() method that does not allocate a new string, but for now, I think, using the trim method is a good solution to avoid unnecessary memory allocation. No?
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 about what i wrote about filtering out empty lines?
Do you agree?