{"record":{"id":"c7363d5baa50c30c","repo":"nautechsystems/nautilus_trader","slug":"c-string-json-must-be-an-array-of-strings","errorCode":null,"errorMessage":"C string JSON must be an array of strings","messagePattern":"C string JSON must be an array of strings","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/core/src/ffi/parsing.rs","lineNumber":65,"sourceCode":"/// # Panics\n///\n/// Panics if `ptr` is null, contains invalid UTF-8/JSON, or the JSON value\n/// is not an array of strings.\n#[must_use]\npub unsafe fn bytes_to_string_vec(ptr: *const c_char) -> Vec<String> {\n    assert!(!ptr.is_null(), \"`ptr` was NULL\");\n\n    // SAFETY: Caller guarantees ptr is valid per function contract\n    let c_str = unsafe { CStr::from_ptr(ptr) };\n    let bytes = c_str.to_bytes();\n\n    let json_string = std::str::from_utf8(bytes).expect(\"C string contains invalid UTF-8\");\n    let value: serde_json::Value =\n        serde_json::from_str(json_string).expect(\"C string contains invalid JSON\");\n\n    let arr = value\n        .as_array()\n        .expect(\"C string JSON must be an array of strings\");\n\n    arr.iter()\n        .map(|value| {\n            value\n                .as_str()\n                .expect(\"C string JSON array must contain only strings\")\n                .to_owned()\n        })\n        .collect()\n}\n\n/// Convert a slice of `String` into a C string pointer (JSON encoded).\n///\n/// # Panics\n///\n/// Panics if JSON serialization fails or if the generated string contains interior null bytes.\n#[must_use]\npub fn string_vec_to_bytes(strings: &[String]) -> *const c_char {","sourceCodeStart":47,"sourceCodeEnd":83,"githubUrl":"https://github.com/nautechsystems/nautilus_trader/blob/18893faf8b356be3320add8de2f861b0b647cf06/crates/core/src/ffi/parsing.rs#L47-L83","documentation":"Panic in `bytes_to_string_vec` when the decoded JSON value is not a JSON array. The function does `value.as_array().expect(\"C string JSON must be an array of strings\")`, so a valid-JSON payload of any other shape (object, string, number) aborts. The FFI contract fixes the wire format to a JSON array of strings.","triggerScenarios":"Passing a C string containing valid JSON that is not an array — e.g. `\"{\\\"a\\\": 1}\"` or `\"\\\"hello\\\"\"` — into `bytes_to_string_vec`, directly or through `synthetic_instrument_new` / `synthetic_instrument_is_valid_formula`.","commonSituations":"Changing the FFI payload format on one side (object instead of array) during refactors; hand-assembling JSON in another language and forgetting the array wrapper; forwarding a JSON object of parameters where a list of component strings is expected.","solutions":["Serialize a `Vec<String>`/list of strings (not an object) on the producing side via `string_vec_to_bytes`","Wrap non-array JSON payloads in an array before crossing the boundary","Version-pin both sides of the FFI boundary so the payload format matches","Add a debug assertion that the payload starts with '[' during development"],"exampleFix":"// before\n{\"components\": [\"a\", \"b\"]}  // object, not array\n// after\n[\"a\", \"b\"]  // JSON array of strings","handlingStrategy":"type-guard","validationCode":"if isinstance(payload, list) and json.dumps(payload).lstrip().startswith('['):\n    ptr = string_vec_to_bytes(payload)\nelse:\n    raise ValueError(\"FFI payload must be a JSON array of strings\")","typeGuard":"fn is_string_array(v: &serde_json::Value) -> bool { v.as_array().map_or(false, |a| !a.is_empty() || true) && v.is_array() }","tryCatchPattern":"let parsed: serde_json::Value = serde_json::from_str(text).unwrap();\nif !parsed.is_array() { return Err(ParsingError::NotAnArray); }","preventionTips":["Serialize lists of strings, never objects, across this boundary","Schema-check the JSON payload in tests before crossing FFI","Version-lock producer and consumer of the payload format","Log the raw payload when debugging boundary mismatches"],"tags":["rust","ffi","json","panic","parsing"],"backgroundTag":"unexpected-response-shape","analyzedSha":"18893faf8b356be3320add8de2f861b0b647cf06","analyzedAt":"2026-09-08T20:49:34.690Z","contentChangedAt":"2026-09-08T20:49:34.690Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}