Skip to content

fix(http_request): JSON-encode string variables when building a JSON body - #235

Open
Mr-Neutr0n wants to merge 1 commit into
langgenius:mainfrom
Mr-Neutr0n:fix/json-body-variable-escaping
Open

fix(http_request): JSON-encode string variables when building a JSON body#235
Mr-Neutr0n wants to merge 1 commit into
langgenius:mainfrom
Mr-Neutr0n:fix/json-body-variable-escaping

Conversation

@Mr-Neutr0n

Copy link
Copy Markdown

Ports the fix from langgenius/dify#32317, which was opened before this node moved out of dify into graphon. The defect is unchanged here. _init_json_body (executor.py:275) still does convert_template(...).text then repair_json.

Originally reported as langgenius/dify#31927, which a stale bot closed as not_planned on 2026-05-05 without a fix.

The bug

The template the workflow author writes is valid JSON:

{"model": "pro", "messages": [{"role": "user", "content": {{#node.result#}}}]}

convert_template(...).text concatenates the raw value in, so a string carrying a quote, backslash or newline (ordinary markdown out of a Code node) breaks the surrounding document. repair_json then does not repair it — it parses the wreckage down to an empty string:

substituted:  {"model": "pro", "messages": [{"role": "user", "content": ### a\n\n| a | ...
valid JSON after substitution?  NO — Expecting value
after repair_json:              content = ''      <-- the entire value is gone

The request goes out with "content": "". No error, no warning; the model just receives nothing.

There's a second, unreported variant. With {"content": "prefix {{#node.result#}} suffix"} and value a"b, the repair pass injects a character rather than dropping one, yielding prefix a"b suffix".

The fix

Encode each string value with json.dumps at the point of substitution. The document stays valid, so no repair is needed and the value round-trips byte for byte.

A variable already inside a string literal gets the escaped body without the quotes json.dumps would add. Quote state is tracked across the literal parts (_json_string_open_after) rather than sniffed from the adjacent characters, so a variable embedded partway through a string behaves the same as one standing alone.

repair_json is retained as a fallback for templates that were already malformed before substitution, which escaping cannot fix. That keeps existing workflows working, and after this change the repair path is no longer load-bearing for well-formed templates.

_log_json_text is rendered through the same encoder with masked=True, so the log copy stays valid JSON and secrets stay obfuscated.

Tests

Six cases in tests/nodes/http_request/test_dispatch.py. Against unpatched main, three fail and the other three pass, so they pin the change rather than the implementation:

test on main
..._preserves_a_string_containing_json_metacharacters (the reported bug) fails — value is ''
..._preserves_a_string_embedded_partway_through_a_literal failsprefix a"b suffix"
..._encodes_the_log_copy_the_same_way fails
..._preserves_a_string_the_author_already_quoted passes — guards against a regression here
..._keeps_non_string_values_as_json_tokens passes — numbers/bools/objects unchanged
..._still_repairs_a_template_that_was_malformed_to_begin_with passes — back-compat for the repair path

Full suite: 678 passed (tests/http/test_client.py skipped locally, it needs pytest-mock). ruff check and ruff format --check clean.

Note on scope

@MezentsevIlya argued on the dify PR that the JSON repairing is an anti-pattern and should be removed or made switchable. I think that's right in direction, and this change is a step toward it rather than away: once substitution stops producing invalid JSON, repair_json stops mattering for anyone writing a valid template. Whether to then drop the fallback entirely is a separate, breaking call that's yours to make — happy to follow up with that if you want it.

…body

convert_template(...).text concatenates raw values into the template, so
a string carrying a quote, backslash or newline breaks the surrounding
JSON. repair_json then does not repair it: for the case in
langgenius/dify#31927 it parses the wreckage down to an empty string, so
the node silently posts "content": "" to the upstream API.

The template the author wrote is valid JSON. Substitution is what breaks
it, so encode each string value with json.dumps at the point of
substitution. The document stays valid, no repair is needed, and the
value round-trips byte for byte.

A variable already inside a string literal gets the escaped body without
the quotes json.dumps would add. Quote state is tracked across the
literal parts rather than sniffed from the adjacent characters, so a
variable embedded partway through a string behaves the same as one
standing alone.

repair_json is retained as a fallback for templates that were already
malformed before substitution, which escaping cannot fix.

Signed-off-by: Harikrishna KP <harikp2002@gmail.com>
@dosubot dosubot Bot added size:M This PR changes 30-99 lines, ignoring generated files. bug Something isn't working labels Jul 31, 2026
@github-actions

github-actions Bot commented Jul 31, 2026

Copy link
Copy Markdown

All contributors on this pull request have signed the CLA.
Posted by the CLA Assistant Lite bot.

@Mr-Neutr0n

Copy link
Copy Markdown
Author

I have read the CLA Document and I hereby sign the CLA

@Mr-Neutr0n

Copy link
Copy Markdown
Author

recheck

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working size:M This PR changes 30-99 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant