nautechsystems/nautilus_trader · error
canonical run field '{field}' must be a decimal string
Error message
canonical run field '{field}' must be a decimal string What it means
`validate_unsigned_decimal` requires canonical run numeric fields (iterations, total_events, total_orders, total_positions) and timestamp fields (backtest_start_ns, backtest_end_ns) to be encoded as JSON strings containing decimal digits. This error is thrown when the field's JSON value is not a string at all (e.g. a number or null for a non-nullable field). The library encodes large integers as strings to preserve exact values.
Source
Thrown at crates/backtest/src/result.rs:478
"canonical trader ID must be a string"
);
anyhow::ensure!(
matches!(
object.get("outcome").and_then(Value::as_str),
Some("completed" | "failed" | "incomplete" | "stopped")
),
"unsupported canonical run outcome"
);
Ok(())
}
fn validate_unsigned_decimal(value: &Value, field: &str, nullable: bool) -> anyhow::Result<()> {
if nullable && value.is_null() {
return Ok(());
}
let value = value
.as_str()
.ok_or_else(|| anyhow::anyhow!("canonical run field '{field}' must be a decimal string"))?;
anyhow::ensure!(
value == "0"
|| (!value.is_empty()
&& !value.starts_with('0')
&& value.bytes().all(|byte| byte.is_ascii_digit())),
"canonical run field '{field}' is not a canonical unsigned decimal"
);
Ok(())
}
fn validate_statistics(value: &Value) -> anyhow::Result<()> {
let object = value
.as_object()
.ok_or_else(|| anyhow::anyhow!("canonical result statistics must be an object"))?;
validate_fields(
object,
&["general", "pnls", "returns", "returns_series"],
"canonical result statistics",View on GitHub (pinned to 18893faf8b)
Solutions
- Encode the field as a decimal string, e.g. "42" instead of 42
- Regenerate the document via the library's canonical result writer (which applies string encoding)
- Pre-transform the JSON to stringify numeric fields before validation
- Check the nullable flag: null is only allowed for backtest_start_ns/backtest_end_ns
Example fix
// before
{"run": {"iterations": 100, "backtest_start_ns": null}}
// after
{"run": {"iterations": "100", "backtest_start_ns": "1720000000000000000"}} Defensive patterns
Strategy: validation
Validate before calling
fn run_decimal_fields_are_strings(run: &serde_json::Value) -> bool {
["iterations", "total_events", "total_orders", "total_positions",
"backtest_start_ns", "backtest_end_ns"]
.iter()
.all(|k| run.get(*k).map_or(false, serde_json::Value::is_string))
} Type guard
fn is_decimal_string(v: &serde_json::Value) -> bool {
v.as_str().map_or(false, |s| !s.is_empty() && s.bytes().all(|b| b.is_ascii_digit()))
} Try / catch
match validate_document(&doc) {
Err(e) if e.to_string().contains("must be a decimal string") => {
// stringify numeric run fields and retry
}
other => other,
} Prevention
- Encode all canonical integers as JSON strings, never raw numbers
- Use the canonical writer's string-encoding helpers (e.g. nanos.to_string())
- Disable default numeric serialization when exporting results
- Test fixtures with the validator before shipping them
When it happens
Trigger: Validating a canonical result where a run field like "iterations", "total_orders", or "backtest_start_ns" is a JSON number (e.g. 42 instead of "42") or null where null is not allowed. Happens with JSON produced by a default serializer that emits raw numbers.
Common situations: Results serialized by generic JSON tools rather than the canonical writer; fixtures where the ns timestamps were written as numbers; null backtest_start_ns/backtest_end_ns passed where the field is required non-null (nullable=false for iterations/total_* fields).
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
- canonical backtest result violates the version 1 encoding ru
- canonical backtest result must be a JSON object
- canonical result field '{key}' must be an array
- canonical result summary must be an object
- canonical run configuration ID must be a string or null
AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08).
Data as JSON: /api/errors/53252c0165bf7101.
Report an issue: GitHub.