{"record":{"id":"363db48f5a3206e9","repo":"nautechsystems/nautilus_trader","slug":"serde-json-deserialization-error-e-363db4","errorCode":null,"errorMessage":"serde_json deserialization error: {e}","messagePattern":"serde_json deserialization error: (.+?)","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/adapters/coinbase/src/provider.rs","lineNumber":130,"sourceCode":"            .await\n            .map_err(|e| anyhow::anyhow!(\"Failed to fetch product '{product_id}': {e}\"))?;\n\n        self.load_from_product_response(&json)\n    }\n\n    /// Parses a products list response and caches the instruments.\n    ///\n    /// Expects the JSON shape returned by `GET /products`: `{\"products\": [...]}`.\n    ///\n    /// # Errors\n    ///\n    /// Returns an error if the JSON cannot be deserialized or any product fails to parse.\n    pub fn load_from_products_response(\n        &self,\n        json: &serde_json::Value,\n    ) -> anyhow::Result<Vec<InstrumentAny>> {\n        let response: ProductsResponse =\n            serde_json::from_value(json.clone()).map_err(|e| anyhow::anyhow!(\"{e}\"))?;\n\n        let instruments = self.parse_and_cache_products(&response.products)?;\n        // Populate the alias map so subscribe paths (which only see the parsed\n        // `InstrumentAny`) can resolve a caller-supplied product id back to the\n        // canonical id Coinbase uses on the wire.\n        self.client.record_product_aliases(&response.products);\n        Ok(instruments)\n    }\n\n    /// Parses a products list response, filtering by product type, and caches the instruments.\n    ///\n    /// # Errors\n    ///\n    /// Returns an error if the JSON cannot be deserialized or any product fails to parse.\n    pub fn load_from_products_response_filtered(\n        &self,\n        json: &serde_json::Value,\n        product_type: CoinbaseProductType,","sourceCodeStart":112,"sourceCodeEnd":148,"githubUrl":"https://github.com/nautechsystems/nautilus_trader/blob/18893faf8b356be3320add8de2f861b0b647cf06/crates/adapters/coinbase/src/provider.rs#L112-L148","documentation":"After a successful products fetch, load_from_products_response deserializes the raw JSON into the typed ProductsResponse model with serde_json::from_value. If the JSON does not match the expected schema (missing required fields, wrong types, unexpected nulls), serde's error is re-raised as this anyhow error. It means the response was received but its shape did not fit the adapter's model.","triggerScenarios":"Calling load_from_products_response(json) (directly in tests/processors, or via load_all) with a JSON value missing fields the Product struct requires, with incompatible types (string vs number), or from a Coinbase API version whose schema changed.","commonSituations":"Coinbase adding/removing fields and the adapter's structs lagging behind; hand-crafted or recorded fixture JSON used in tests; passing a single-product JSON object where a products-list response is expected.","solutions":["Log/inspect serde's message ({e}) to identify the exact field and expected type.","Compare the failing JSON against the adapter's ProductsResponse/Product struct definitions.","Update the model structs or add serde defaults/skippable attributes if the Coinbase schema changed.","Ensure the value passed in is a full products-list response, not a single product or error body."],"exampleFix":"// before\nlet v: serde_json::Value = serde_json::from_str(raw)?;\nlet instruments = provider.load_from_products_response(&v)?;\n// after — guard shape first\nlet v: serde_json::Value = serde_json::from_str(raw)?;\nanyhow::ensure!(v.get(\"products\").map_or(false, |p| p.is_array()), \"not a products response\");\nlet instruments = provider.load_from_products_response(&v)?;","handlingStrategy":"type-guard","validationCode":"fn looks_like_products_response(v: &serde_json::Value) -> bool {\n    v.get(\"products\").map_or(false, |p| p.is_array())\n}\nanyhow::ensure!(looks_like_products_response(&json), \"payload is not a products response\");","typeGuard":"fn is_products_response(v: &serde_json::Value) -> bool { v.get(\"products\").map_or(false, serde_json::Value::is_array) }","tryCatchPattern":"match serde_json::from_value::<ProductsResponse>(json.clone()) {\n    Ok(resp) => process(resp),\n    Err(e) => { error!(\"schema mismatch: {e}\"); log_raw_payload(&json); }\n}","preventionTips":["Log the raw payload whenever deserialization fails to spot schema drift quickly.","Add round-trip fixture tests that deserialize real Coinbase responses.","Pin/track Coinbase API versions and review changelogs for product schema changes."],"tags":["serde","deserialization","coinbase","schema"],"backgroundTag":"json-unmarshal-failed","analyzedSha":"18893faf8b356be3320add8de2f861b0b647cf06","analyzedAt":"2026-09-08T20:49:34.690Z","contentChangedAt":"2026-09-08T20:49:34.690Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}