BurntSushi / BurntSushi/rust-csv

Deserialise behaviour changes when the first field in a struct is None

Open
#390 0 comments 0 reactions 0 assignees View on GitHub
Dominant language
Rust
Stars
2k
Forks
257
PR merge metrics
No merged PRs in 30d

Description

Deserialising into a nested optional structure causes the inner structure to be set to None even when the csv row has data.

given the following structures:
```rust
#[derive(Debug, serde::Deserialize, Clone)]
struct Test {
a: String,
x: Option,
}

#[derive(Debug, serde::Deserialize, Clone)]
struct Test2 {
b: Option,
c: String,
}
```

and a row of data `"a,,c"`, `x` is set to None and `c` is lost. I expect it to deserialise into `Test { a: "a", x: Some(Test2 { b: None, c: "c" }) }` but it deserialises to `Test{ a: "a", x: None }`.

I believe the problem occurs because `Option` peeks at the next field, sees it is empty, and sets `x` to None, and does not consider that the inner type is a map or a slice.

test passes:
```rust
fn test_nested_csv_pass() {
#[derive(Debug, serde::Deserialize, Clone)]
struct Test {
a: String,
x: Option,
}

#[derive(Debug, serde::Deserialize, Clone)]
struct Test2 {
b: String,
c: Option,
}
let data = "a,b,";
let record: Test = csv::ReaderBuilder::new()
.has_headers(false)
.from_reader(data.as_bytes())
.into_records()
.next()
.unwrap()
.unwrap()
.deserialize(None)
.unwrap();

assert!(record.x.is_some());
}
```

tests fails:
```rust

fn test_nested_csv_fails() {
#[derive(Debug, serde::Deserialize, Clone)]
struct Test {
a: String,
x: Option,
}

#[derive(Debug, serde::Deserialize, Clone)]
struct Test2 {
b: Option,
c: String,
}
let data = "a,,c";
let record: Test = csv::ReaderBuilder::new()
.has_headers(false)
.from_reader(data.as_bytes())
.into_records()
.next()
.unwrap()
.unwrap()
.deserialize(None)
.unwrap();

assert!(record.x.is_some());
}

fn test_nested_csv_slice_fails() {
#[derive(Debug, serde::Deserialize, Clone)]
struct Test {
a: String,
x: Option,
}

#[derive(Debug, serde::Deserialize, Clone)]
struct Test2 {
b: [Option; 2],
}
let data = "a,,c";
let record: Test = csv::ReaderBuilder::new()
.has_headers(false)
.from_reader(data.as_bytes())
.into_records()
.next()
.unwrap()
.unwrap()
.deserialize(None)
.unwrap();

assert!(record.x.is_some());
}
```

Contributor guide

No contributing guide indexed for this repository

Research direction

Start at the record deserialization entry point shown by `.deserialize(None)` and reproduce the cases in `test_nested_csv_fails` and `test_nested_csv_slice_fails`. Trace how an empty first nested field affects `Option`; done means both regressions preserve `x` as `Some` and retain later row data.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust
Domain
data
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Stale
Clarity
Clearly specified
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.