BurntSushi / BurntSushi/rust-csv
Deserialise behaviour changes when the first field in a struct is None
- 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