nautechsystems/nautilus_trader · error

C string JSON array must contain only strings

Error message

C string JSON array must contain only strings

What it means

Panic in `bytes_to_string_vec` when the JSON array contains non-string elements. Each element goes through `value.as_str().expect("C string JSON array must contain only strings")`, so numbers, nulls, booleans, or nested arrays inside the array cause a panic. The FFI wire format is strictly `Vec<String>` encoded as JSON.

Source

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

    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 {
    let json_string = serde_json::to_string(strings).expect("Failed to serialize strings to JSON");
    let c_string = CString::new(json_string).expect("JSON string contains interior null bytes");

    c_string.into_raw()
}

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Coerce all elements to strings before serializing (e.g. `[str(x) for x in items]` in Python)
  2. Filter out or stringify None/null entries prior to `string_vec_to_bytes`
  3. Validate the JSON with a schema (`array of string`) at the boundary in debug builds
  4. Ensure the producer language's list is homogeneous before crossing FFI

Example fix

# before (Python side)
components = ["ema", 10, None]
# after
components = [str(x) for x in components if x is not None]
Defensive patterns

Strategy: validation

Validate before calling

if not all(isinstance(x, str) for x in components):
    components = [str(x) for x in components if x is not None]

Type guard

fn all_strings(items: &[serde_json::Value]) -> bool { items.iter().all(|v| v.is_string()) }

Try / catch

let vals: Vec<serde_json::Value> = serde_json::from_str(text)?;
if !vals.iter().all(|v| v.is_string()) { return Err(ParsingError::NonStringElement); }

Prevention

When it happens

Trigger: Passing a C string holding e.g. `["a", 1, null]` into `bytes_to_string_vec`, or through the synthetic-instrument FFI entry points that consume component lists.

Common situations: Serializing a mixed-type list (e.g. Python `list` containing ints/None) on the calling-language side; loose JSON construction in scripts; dynamic data where a component is accidentally `None` and serialized as null.

Understand the failure class

Background: Type mismatch errors: IllegalArgumentException, TypeError and type guards across 150 open-source libraries — this error's family across 150 libraries.

Related errors


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