Skip to content

Commit 30cc9a0

Browse files
committed
Add support for serializing flattened attributes
1 parent 4a3997e commit 30cc9a0

File tree

2 files changed

+141
-21
lines changed

2 files changed

+141
-21
lines changed

src/deserializer.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1205,6 +1205,26 @@ mod tests {
12051205
);
12061206
}
12071207

1208+
#[test]
1209+
fn flatten_map() {
1210+
#[derive(Deserialize, Debug, PartialEq)]
1211+
struct Row {
1212+
x: f64,
1213+
y: f64,
1214+
#[serde(flatten)]
1215+
extra: HashMap<String, f64>,
1216+
}
1217+
1218+
let header = StringRecord::from(vec!["x", "y", "prop1", "prop2"]);
1219+
let record = StringRecord::from(vec!["1", "2", "3", "4"]);
1220+
let got: Row = record.deserialize(Some(&header)).unwrap();
1221+
let mut extra = HashMap::new();
1222+
extra.insert("prop1".to_string(), 3.0);
1223+
extra.insert("prop2".to_string(), 4.0);
1224+
1225+
assert_eq!(got, Row { x: 1.0, y: 2.0, extra });
1226+
}
1227+
12081228
#[test]
12091229
fn partially_invalid_utf8() {
12101230
#[derive(Debug, Deserialize, PartialEq)]

src/serializer.rs

Lines changed: 121 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -200,12 +200,7 @@ impl<'a, 'w, W: io::Write> Serializer for &'a mut SeRecord<'w, W> {
200200
self,
201201
_len: Option<usize>,
202202
) -> Result<Self::SerializeMap, Self::Error> {
203-
// The right behavior for serializing maps isn't clear.
204-
Err(Error::custom(
205-
"serializing maps is not supported, \
206-
if you have a use case, please file an issue at \
207-
https://github.com/BurntSushi/rust-csv",
208-
))
203+
Ok(self)
209204
}
210205

211206
fn serialize_struct(
@@ -299,18 +294,18 @@ impl<'a, 'w, W: io::Write> SerializeMap for &'a mut SeRecord<'w, W> {
299294
&mut self,
300295
_key: &T,
301296
) -> Result<(), Self::Error> {
302-
unreachable!()
297+
Ok(())
303298
}
304299

305300
fn serialize_value<T: ?Sized + Serialize>(
306301
&mut self,
307-
_value: &T,
302+
value: &T,
308303
) -> Result<(), Self::Error> {
309-
unreachable!()
304+
value.serialize(&mut **self)
310305
}
311306

312307
fn end(self) -> Result<Self::Ok, Self::Error> {
313-
unreachable!()
308+
Ok(())
314309
}
315310
}
316311

@@ -646,12 +641,7 @@ impl<'a, 'w, W: io::Write> Serializer for &'a mut SeHeader<'w, W> {
646641
self,
647642
_len: Option<usize>,
648643
) -> Result<Self::SerializeMap, Self::Error> {
649-
// The right behavior for serializing maps isn't clear.
650-
Err(Error::custom(
651-
"serializing maps is not supported, \
652-
if you have a use case, please file an issue at \
653-
https://github.com/BurntSushi/rust-csv",
654-
))
644+
self.handle_container("map")
655645
}
656646

657647
fn serialize_struct(
@@ -743,20 +733,37 @@ impl<'a, 'w, W: io::Write> SerializeMap for &'a mut SeHeader<'w, W> {
743733

744734
fn serialize_key<T: ?Sized + Serialize>(
745735
&mut self,
746-
_key: &T,
736+
key: &T,
747737
) -> Result<(), Self::Error> {
748-
unreachable!()
738+
// Grab old state and update state to `EncounteredStructField`.
739+
let old_state =
740+
mem::replace(&mut self.state, HeaderState::EncounteredStructField);
741+
if let HeaderState::ErrorIfWrite(err) = old_state {
742+
return Err(err);
743+
}
744+
745+
let mut key_serializer = SeRecord { wtr: self.wtr };
746+
key.serialize(&mut key_serializer)?;
747+
self.state = HeaderState::InStructField;
748+
Ok(())
749749
}
750750

751751
fn serialize_value<T: ?Sized + Serialize>(
752752
&mut self,
753-
_value: &T,
753+
value: &T,
754754
) -> Result<(), Self::Error> {
755-
unreachable!()
755+
if !matches!(self.state, HeaderState::InStructField) {
756+
return Err(Error::new(ErrorKind::Serialize(
757+
"Attempted to serialize value without key".to_string(),
758+
)));
759+
}
760+
value.serialize(&mut **self)?;
761+
self.state = HeaderState::EncounteredStructField;
762+
Ok(())
756763
}
757764

758765
fn end(self) -> Result<Self::Ok, Self::Error> {
759-
unreachable!()
766+
Ok(())
760767
}
761768
}
762769

@@ -809,6 +816,8 @@ impl<'a, 'w, W: io::Write> SerializeStructVariant for &'a mut SeHeader<'w, W> {
809816

810817
#[cfg(test)]
811818
mod tests {
819+
use std::collections::BTreeMap;
820+
812821
use {bstr::ByteSlice, serde::Serialize};
813822

814823
use crate::{
@@ -1325,4 +1334,95 @@ mod tests {
13251334
assert!(wrote);
13261335
assert_eq!(got, "label,num,label2,value,empty,label,num");
13271336
}
1337+
1338+
#[test]
1339+
fn flatten() {
1340+
#[derive(Clone, Serialize, Debug, PartialEq)]
1341+
struct Input {
1342+
x: f64,
1343+
y: f64,
1344+
}
1345+
1346+
#[derive(Clone, Serialize, Debug, PartialEq)]
1347+
struct Properties {
1348+
prop1: f64,
1349+
prop2: f64,
1350+
}
1351+
1352+
#[derive(Clone, Serialize, Debug, PartialEq)]
1353+
struct Row {
1354+
#[serde(flatten)]
1355+
input: Input,
1356+
#[serde(flatten)]
1357+
properties: Properties,
1358+
}
1359+
let row = Row {
1360+
input: Input { x: 1.0, y: 2.0 },
1361+
properties: Properties { prop1: 3.0, prop2: 4.0 },
1362+
};
1363+
1364+
let got = serialize(row.clone());
1365+
assert_eq!(got, "1.0,2.0,3.0,4.0\n");
1366+
1367+
let (wrote, got) = serialize_header(row.clone());
1368+
assert!(wrote);
1369+
assert_eq!(got, "x,y,prop1,prop2");
1370+
}
1371+
1372+
#[test]
1373+
fn flatten_map() {
1374+
#[derive(Clone, Serialize, Debug, PartialEq)]
1375+
struct Row {
1376+
x: f64,
1377+
y: f64,
1378+
#[serde(flatten)]
1379+
extra: BTreeMap<&'static str, f64>,
1380+
}
1381+
let mut extra = BTreeMap::new();
1382+
extra.insert("extra1", 3.0);
1383+
extra.insert("extra2", 4.0);
1384+
let row = Row { x: 1.0, y: 2.0, extra };
1385+
1386+
let got = serialize(row.clone());
1387+
assert_eq!(got, format!("1.0,2.0,3.0,4.0\n"));
1388+
1389+
let (wrote, got) = serialize_header(row.clone());
1390+
assert!(wrote);
1391+
assert_eq!(got, format!("x,y,extra1,extra2"));
1392+
}
1393+
1394+
#[test]
1395+
fn flatten_map_different_num_entries() {
1396+
#[derive(Clone, Serialize, Debug, PartialEq)]
1397+
struct Row {
1398+
x: f64,
1399+
y: f64,
1400+
#[serde(flatten)]
1401+
extra: BTreeMap<&'static str, f64>,
1402+
}
1403+
let mut wtr = Writer::from_writer(vec![]);
1404+
1405+
let mut extra = BTreeMap::new();
1406+
extra.insert("extra1", 3.0);
1407+
extra.insert("extra2", 4.0);
1408+
let row = Row { x: 1.0, y: 2.0, extra };
1409+
wtr.serialize(row).unwrap();
1410+
1411+
let mut extra = BTreeMap::new();
1412+
extra.insert("extra3", 3.0);
1413+
extra.insert("extra4", 4.0);
1414+
extra.insert("extra5", 5.0);
1415+
let row = Row { x: 1.0, y: 2.0, extra };
1416+
let error = wtr.serialize(row).unwrap_err();
1417+
match *error.kind() {
1418+
ErrorKind::UnequalLengths {
1419+
pos: None,
1420+
expected_len: 4,
1421+
len: 5,
1422+
} => {}
1423+
ref x => {
1424+
panic!("expected ErrorKind::UnequalLengths but got '{x:?}'")
1425+
}
1426+
}
1427+
}
13281428
}

0 commit comments

Comments
 (0)