From a44cd27c2e16e13a98b48b4ceed89c82701fa484 Mon Sep 17 00:00:00 2001 From: Vito Date: Mon, 19 Oct 2026 22:04:32 -0700 Subject: [PATCH] fix(parse): build array String() without recursion (#255) utils.generation_params converted nested arrays to strings recursively, so a crafted file with a value nested about 900 arrays deep raised RecursionError, where the browser parser returns a result. The join now runs on an explicit stack. A JS-parity case with 900-deep values pins it. --- tests/fixtures/generation_params/vectors.json | 17 +++++++++++++ tests/generation_params_cases.py | 17 +++++++++++++ tests/test_generation_params.py | 6 +++++ utils/generation_params.py | 25 ++++++++++++++++++- 4 files changed, 64 insertions(+), 1 deletion(-) diff --git a/tests/fixtures/generation_params/vectors.json b/tests/fixtures/generation_params/vectors.json index 6839186..0f7097e 100644 --- a/tests/fixtures/generation_params/vectors.json +++ b/tests/fixtures/generation_params/vectors.json @@ -364,6 +364,23 @@ "prompt": "{\"4\": {\"class_type\": \"CheckpointLoaderSimple\", \"inputs\": {\"ckpt_name\": \"example.safetensors\"}}, \"6\": {\"class_type\": \"CLIPTextEncode\", \"inputs\": {\"text\": \"x\", \"clip\": [\"4\", 1]}}, \"3\": {\"class_type\": \"KSampler\", \"inputs\": {\"seed\": 4974052037430000001, \"steps\": 20, \"cfg\": 1.0, \"sampler_name\": \"euler\", \"scheduler\": \"normal\", \"denoise\": 1.0, \"model\": [\"4\", 0], \"positive\": [\"6\", 0], \"negative\": [\"6\", 0], \"latent_image\": [\"2\", 0]}}}", "workflow": null }, + { + "expected": { + "ok": true, + "result": { + "cfgScale": 7, + "checkpoint": "example.safetensors", + "negativePrompt": "nested neighbours", + "positivePrompt": "nested neighbours", + "sampler": "euler", + "seed": 5, + "steps": 20 + } + }, + "name": "case:deeply-nested-values", + "prompt": "{\"4\": {\"class_type\": \"CheckpointLoaderSimple\", \"inputs\": {\"ckpt_name\": \"example.safetensors\"}}, \"6\": {\"class_type\": \"CLIPTextEncode\", \"inputs\": {\"text\": \"nested neighbours\", \"clip\": [\"4\", 1]}}, \"3\": {\"class_type\": \"KSampler\", \"inputs\": {\"seed\": 5, \"steps\": 20, \"cfg\": 7, \"sampler_name\": \"euler\", \"scheduler\": \"normal\", \"denoise\": 1.0, \"model\": [\"4\", 0], \"positive\": [\"6\", 0], \"negative\": [\"6\", 0], \"latent_image\": [\"2\", 0]}}, \"8\": {\"class_type\": [[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[\"leaf\", 7], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], \"inputs\": {\"text\": \"x\"}}}", + "workflow": "{\"nodes\": [{\"id\": [[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[\"leaf\", 7], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], \"type\": [[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[\"leaf\", 7], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null], null]}], \"links\": []}" + }, { "expected": { "ok": true, diff --git a/tests/generation_params_cases.py b/tests/generation_params_cases.py index da76420..debdc7c 100644 --- a/tests/generation_params_cases.py +++ b/tests/generation_params_cases.py @@ -244,6 +244,14 @@ def _workflow_only(): } +def _nested(depth): + """A value nested ``depth`` arrays deep, as a crafted file can hold.""" + value = ["leaf", 7] + for _ in range(depth): + value = [value, None] + return value + + def _chain(length): graph = {"4": CHECKPOINT, "1": encoder("deep chain text")} previous = "1" @@ -440,5 +448,14 @@ CASES = { "3": ksampler(("6", 0), ("6", 0), cfg=1.0, seed=4974052037430000001), } ), + "deeply-nested-values": case( + { + "4": CHECKPOINT, + "6": encoder("nested neighbours"), + "3": ksampler(("6", 0), ("6", 0)), + "8": {"class_type": _nested(900), "inputs": {"text": "x"}}, + }, + {"nodes": [{"id": _nested(900), "type": _nested(900)}], "links": []}, + ), "nothing": case(), } diff --git a/tests/test_generation_params.py b/tests/test_generation_params.py index d925bed..990d3a6 100644 --- a/tests/test_generation_params.py +++ b/tests/test_generation_params.py @@ -176,6 +176,12 @@ class StaysBounded(unittest.TestCase): self.assertEqual(result["negativePrompt"], "t") +class DeeplyNestedValues(unittest.TestCase): + def test_deeply_nested_values_do_not_exhaust_the_python_stack(self): + result = run("deeply-nested-values") + self.assertEqual(result["positivePrompt"], "nested neighbours") + + class Placeholders(unittest.TestCase): def test_blank_text_is_a_placeholder_and_zero_seed_is_kept(self): result = run("blank-text-and-zero-seed") diff --git a/utils/generation_params.py b/utils/generation_params.py index e039753..e65ecd1 100644 --- a/utils/generation_params.py +++ b/utils/generation_params.py @@ -217,10 +217,33 @@ def _js_string(value): if isinstance(value, str): return value if isinstance(value, list): - return ",".join("" if _nullish(item) else _js_string(item) for item in value) + return _array_string(value) return "[object Object]" +def _array_string(value): + """``String(array)``: ``join(",")`` applied to nested arrays, on an explicit stack. + + null and undefined elements join as "". A crafted file can nest arrays far + deeper than the Python stack allows, so this never recurses. + """ + out = [] + stack = [(value, 0)] + while stack: + items, index = stack.pop() + if index == len(items): + continue + if index: + out.append(",") + stack.append((items, index + 1)) + item = items[index] + if isinstance(item, list): + stack.append((item, 0)) + elif not _nullish(item): + out.append(_js_string(item)) + return "".join(out) + + def _key(value): return value if isinstance(value, str) else _js_string(value)