nautechsystems/nautilus_trader · error

C string JSON must be an array of strings

Error message

C string JSON must be an array of strings

What it means

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.

Source

Thrown at crates/core/src/ffi/parsing.rs:65

/// # Panics
///
/// Panics if `ptr` is null, contains invalid UTF-8/JSON, or the JSON value
/// is not an array of strings.
#[must_use]
pub unsafe fn bytes_to_string_vec(ptr: *const c_char) -> Vec<String> {
    assert!(!ptr.is_null(), "`ptr` was NULL");

    // SAFETY: Caller guarantees ptr is valid per function contract
    let c_str = unsafe { CStr::from_ptr(ptr) };
    let bytes = c_str.to_bytes();

    let json_string = std::str::from_utf8(bytes).expect("C string contains invalid UTF-8");
    let value: serde_json::Value =
        serde_json::from_str(json_string).expect("C string contains invalid JSON");

    let arr = value
        .as_array()
        .expect("C string JSON must be an array of strings");

    arr.iter()
        .map(|value| {
            value
                .as_str()
                .expect("C string JSON array must contain only strings")
                .to_owned()
        })
        .collect()
}

/// Convert a slice of `String` into a C string pointer (JSON encoded).
///
/// # Panics
///
/// Panics if JSON serialization fails or if the generated string contains interior null bytes.
#[must_use]
pub fn string_vec_to_bytes(strings: &[String]) -> *const c_char {

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Serialize a `Vec<String>`/list of strings (not an object) on the producing side via `string_vec_to_bytes`
  2. Wrap non-array JSON payloads in an array before crossing the boundary
  3. Version-pin both sides of the FFI boundary so the payload format matches
  4. Add a debug assertion that the payload starts with '[' during development

Example fix

// before
{"components": ["a", "b"]}  // object, not array
// after
["a", "b"]  // JSON array of strings
Defensive patterns

Strategy: type-guard

Validate before calling

if isinstance(payload, list) and json.dumps(payload).lstrip().startswith('['):
    ptr = string_vec_to_bytes(payload)
else:
    raise ValueError("FFI payload must be a JSON array of strings")

Type guard

fn is_string_array(v: &serde_json::Value) -> bool { v.as_array().map_or(false, |a| !a.is_empty() || true) && v.is_array() }

Try / catch

let parsed: serde_json::Value = serde_json::from_str(text).unwrap();
if !parsed.is_array() { return Err(ParsingError::NotAnArray); }

Prevention

When it happens

Trigger: 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`.

Common situations: 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.

Related errors


AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08). Data as JSON: /api/errors/c7363d5baa50c30c. Report an issue: GitHub.