Debugging Intermittent JSON Parsing Failures with serde
Encountered an odd issue when calling an external API—JSON deserialization worked on the first call but then started failing intermittently. The error message trailing input at line 1 column 690 kept appearing on subsequent calls.
Reproducing the Issue
First invocation succeeded:
>$ cnb news list
iPhone 18 Pro Still Uses Centered Smaller Dynamic Island, Apple AI Pin Expected Next Year
...
[#813396][posted@ [4 hours ago]][views: 13][comments: 0][likes: 0]
Immediately running the same command again:
>$ cnb news list
Error: error decoding response body
Caused by:
trailing input at line 1 column 690
The parser claims there's extra contant after valid JSON, which seemed impossible.
Examining the Raw JSON Response
Grabbed the raw response from the API:
[{"Id":813396,"Title":"iPhone 18 Pro Still Uses Centered Smaller Dynamic Island...","DateAdded":"2026-01-22T14:18:00+08:00"},...]
The JSON validated correctly online. Both the JSON and parsing code appeared fine, which usual means something else is wrong.
Added debug logging to inspect the raw response and parsing results:
use anyhow::Result;
use reqwest::{Client, Response};
use serde::Serialize;
use crate::{api::urls::OPENAPI, models::news::NewsInfo, tools::IntoAnyhowResult};
pub async fn list_news(c: &Client, page: impl Serialize + Send + Sync) -> Result<Vec<NewsInfo>> {
let res = raw_list_news(c, page).await?.text().await?;
println!("res: {}", res);
Ok(serde_json::from_str(&res)?)
}
pub async fn raw_list_news(c: &Client, page: impl Serialize + Send + Sync) -> Result<Response> {
let url = format!("{}/{}", OPENAPI, "NewsItems");
c.get(url).query(&page).send().await.into_anyhow_result()
}
>$ cargo run --bin cnb -- news list --page-index 2 --page-size 12
res: [{"Id":813396,..., "DateAdded":"2026-01-22T14:18:00"},...]
[iPhone 18 Pro Still Uses Centered Smaller Dynamic Island...]
...
>$ cargo run --bin cnb -- news list --page-index 2 --page-size 12
res: [{"Id":813396,..., "DateAdded":"2026-01-22T14:18:00+08:00"},...]
Error: trailing input at line 1 column 690
Both calls returned complete JSON, so the issue wasn't truncated responses.
Inspecting the Struct Definition
use chrono::NaiveDateTime;
use serde::Deserialize;
#[derive(Debug, Deserialize, Default)]
#[serde(rename_all = "PascalCase")]
#[serde(default)]
pub struct NewsInfo {
pub id: u64,
pub title: String,
pub summary: String,
pub topic_id: u64,
pub topic_icon: String,
pub view_count: u64,
pub comment_count: u64,
pub digg_count: u64,
pub date_added: NaiveDateTime,
}
Incremental Debugging
Parsed the JSON into serde_json::Value first, then deserialized each item individually:
pub async fn list_news(c: &Client, page: impl Serialize + Send + Sync) -> Result<Vec<NewsInfo>> {
let res = raw_list_news(c, page).await?.text().await?;
println!("res: {}", res);
let values: Vec<serde_json::Value> = serde_json::from_str(&res)?;
for (i, item) in values.iter().enumerate() {
if let Err(e) = serde_json::from_value::<NewsInfo>(item.clone()) {
eprintln!("❌ failed at index {i}: {e}");
eprintln!("{}", item);
break;
}
}
Ok(serde_json::from_str(&res)?)
}
Second call error:
failed at index 0: trailing input
{"CommentCount":0,"DateAdded":"2026-01-22T14:18:00+08:00",...}
The issue surfaced on DateAdded. The struct expects NaiveDateTime (no timezone), but the API returned "2026-01-22T14:18:00+08:00" with timezone information.
First call had:
"DateAdded":"2026-01-22T14:18:00"
Without timezone. The API returns inconsistent date formats between calls.
Changed date_added to String for testing:
failed at index 6: invalid type: null, expected a string
{"TopicIcon":null,...}
This time TopicIcon was null. The struct expected String, not Option<String>.
The #[serde(default)] attribute only applies when fields are missing entirely. A JSON null maps to Option::None, not the default value.
Final struct fix:
#[derive(Debug, Deserialize, Default)]
#[serde(rename_all = "PascalCase")]
#[serde(default)]
pub struct NewsInfo {
pub id: u64,
pub title: String,
pub summary: String,
pub topic_id: u64,
pub topic_icon: Option<String>,
pub view_count: u64,
pub comment_count: u64,
pub digg_count: u64,
pub date_added: String,
}
This "mysterious" parsing failure came down to two API inconsistencies: unstable datetime formats and null values appearing where strings were expected.