Loading page…
Rust walkthroughs
Loading page…
serde::ser::SerializeMap::end finalize map serialization for custom serializers?serde::ser::SerializeMap::end completes the map serialization process by signaling to the serializer that all key-value pairs have been emitted, allowing the serializer to write any closing delimiters and flush buffered content. When implementing Serialize for custom types that serialize as maps, the pattern requires calling serialize_map to obtain a SerializeMap instance, calling serialize_key and serialize_value (or serialize_entry) for each key-value pair, and finally calling end to finalize the output. The end method returns the final result from the serializer, which may be () for formats like JSON that stream directly, or may return collected data for formats that buffer output.
use serde::ser::{Serialize, Serializer, SerializeMap};
struct KeyValue {
data: Vec<(String, i32)>,
}
impl Serialize for KeyValue {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
// Start map serialization with known length
let mut map = serializer.serialize_map(Some(self.data.len()))?;
// Serialize each key-value pair
for (key, value) in &self.data {
map.serialize_entry(key, value)?;
}
// Finalize the map - this is required
map.end()
}
}
fn main() {
let kv = KeyValue {
data: vec![
("a".to_string(), 1),
("b".to_string(), 2),
],
};
let json = serde_json::to_string(&kv).unwrap();
println!("{}", json);
// {"a":1,"b":2}
}end closes the map and returns the final serialized output.
use serde::ser::{Serializer, SerializeMap};
// Simplified view of what happens in JSON serialization:
//
// serialize_map(Some(len)) -> Opens "{", returns SerializeMap
// serialize_entry(k, v) -> Writes "key":value,
// end() -> Writes "}", returns Result
// The end() method:
// 1. Writes the closing delimiter (e.g., "}" for JSON)
// 2. Flushes any internal buffers
// 3. Returns the completed outputFor JSON, end writes the closing brace and returns the result.
use serde::ser::{Serialize, Serializer, SerializeMap};
use std::collections::HashMap;
struct Config {
values: HashMap<String, String>,
}
impl Serialize for Config {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut map = serializer.serialize_map(Some(self.values.len()))?;
// Must serialize keys and values in sequence
for (key, value) in &self.values {
map.serialize_entry(key, value)?;
}
// end() must be called to complete serialization
map.end()
}
}All entries must be added before calling end.
use serde::ser::{Serialize, Serializer, SerializeMap};
struct Pair {
key: String,
value: i32,
}
impl Serialize for Pair {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut map = serializer.serialize_map(Some(1))?;
// Option 1: serialize_key + serialize_value
map.serialize_key(&self.key)?;
map.serialize_value(&self.value)?;
map.end()
}
}
// Alternative using serialize_entry (more concise)
impl Serialize for Pair {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut map = serializer.serialize_map(Some(1))?;
// Option 2: serialize_entry (combines key + value)
map.serialize_entry(&self.key, &self.value)?;
map.end()
}
}Use serialize_key/serialize_value for separate calls, or serialize_entry for combined.
use serde::ser::{Serialize, Serializer, SerializeMap};
struct DynamicMap {
entries: Vec<(String, String)>,
}
impl Serialize for DynamicMap {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
// Pass None when length is unknown
let mut map = serializer.serialize_map(None)?;
for (key, value) in &self.entries {
map.serialize_entry(key, value)?;
}
// end() still required regardless of known length
map.end()
}
}Pass None to serialize_map when the number of entries isn't known in advance.
use serde::ser::{Serializer, SerializeMap, Ok, Error};
use std::collections::HashMap;
// A simple serializer that builds a string representation
struct StringSerializer {
output: String,
}
impl Serializer for StringSerializer {
type Ok = String;
type Error = std::fmt::Error;
type SerializeMap = MapSerializer;
// ... other associated types ...
fn serialize_map(self, _len: Option<usize>) -> Result<Self::SerializeMap, Self::Error> {
Ok(MapSerializer {
output: String::new(),
first: true,
})
}
// ... other methods ...
}
struct MapSerializer {
output: String,
first: bool,
}
impl SerializeMap for MapSerializer {
type Ok = String;
type Error = std::fmt::Error;
fn serialize_entry<K, V>(&mut self, key: &K, value: &V) -> Result<(), Self::Error>
where
K: ?Sized + serde::Serialize,
V: ?Sized + serde::Serialize,
{
if !self.first {
self.output.push_str(", ");
}
self.first = false;
// Serialize key and value (simplified)
self.output.push_str(&format!("{:?}: {:?}",
serde_json::to_string(key).unwrap(),
serde_json::to_string(value).unwrap()
));
Ok(())
}
fn end(self) -> Result<Self::Ok, Self::Error> {
// This is where the map is finalized
Ok(format!("{{{}}}", self.output))
}
}Implementing SerializeMap::end defines how the map output is finalized.
use serde::ser::{Serialize, Serializer, SerializeMap};
use std::collections::HashMap;
struct NestedConfig {
sections: HashMap<String, HashMap<String, String>>,
}
impl Serialize for NestedConfig {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut map = serializer.serialize_map(Some(self.sections.len()))?;
for (section_name, settings) in &self.sections {
// Each value is itself a map
map.serialize_entry(section_name, settings)?;
}
map.end()
}
}
fn main() {
let mut sections = HashMap::new();
let mut database = HashMap::new();
database.insert("host".to_string(), "localhost".to_string());
database.insert("port".to_string(), "5432".to_string());
sections.insert("database".to_string(), database);
let config = NestedConfig { sections };
let json = serde_json::to_string(&config).unwrap();
println!("{}", json);
}Nested maps each require their own end call.
use serde::ser::{Serialize, Serializer, SerializeMap};
struct OptionalFields {
required: String,
optional: Option<String>,
conditional: Option<i32>,
}
impl Serialize for OptionalFields {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
// Count actual entries for length hint
let len = 1 + self.optional.is_some() as usize
+ self.conditional.is_some() as usize;
let mut map = serializer.serialize_map(Some(len))?;
map.serialize_entry("required", &self.required)?;
if let Some(ref opt) = self.optional {
map.serialize_entry("optional", opt)?;
}
if let Some(ref cond) = self.conditional {
map.serialize_entry("conditional", cond)?;
}
map.end()
}
}Count actual entries for accurate length hints when fields are optional.
use serde::ser::{Serialize, Serializer, SerializeMap};
struct Data {
values: Vec<(String, String)>,
}
impl Serialize for Data {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut map = serializer.serialize_map(Some(self.values.len()))?;
for (key, value) in &self.values {
// Each call can fail
map.serialize_entry(key, value)?;
}
// If we reach here, all entries succeeded
// end() produces the final result
map.end()
}
}
// If any serialize_entry fails, end is never called
// The error propagates up immediatelyIf serialization fails mid-sequence, end is never reached.
use serde::ser::{Serialize, Serializer, SerializeMap};
use std::collections::BTreeMap;
struct DynamicObject {
fields: BTreeMap<String, Box<dyn erased_serde::Serialize>>,
}
impl Serialize for DynamicObject {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut map = serializer.serialize_map(Some(self.fields.len()))?;
for (key, value) in &self.fields {
map.serialize_entry(key, value)?;
}
map.end()
}
}
// Note: This requires erased_serde crate for trait objectsDynamic serialization uses the same pattern with type-erased values.
use serde::ser::{Serialize, Serializer, SerializeMap};
struct Person {
name: String,
age: u32,
email: Option<String>,
}
impl Serialize for Person {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
// Structs can be serialized as maps
let field_count = 2 + self.email.is_some() as usize;
let mut map = serializer.serialize_map(Some(field_count))?;
map.serialize_entry("name", &self.name)?;
map.serialize_entry("age", &self.age)?;
if let Some(ref email) = self.email {
map.serialize_entry("email", email)?;
}
map.end()
}
}
fn main() {
let person = Person {
name: "Alice".to_string(),
age: 30,
email: Some("alice@example.com".to_string()),
};
let json = serde_json::to_string(&person).unwrap();
println!("{}", json);
// {"name":"Alice","age":30,"email":"alice@example.com"}
}Structs serialized as maps offer flexibility for optional fields.
use serde::ser::{Serialize, Serializer, SerializeMap};
struct Container {
id: String,
metadata: Metadata,
}
struct Metadata {
created: String,
modified: String,
}
impl Serialize for Container {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut map = serializer.serialize_map(Some(3))?;
map.serialize_entry("id", &self.id)?;
// Flatten metadata fields into the parent map
map.serialize_entry("created", &self.metadata.created)?;
map.serialize_entry("modified", &self.metadata.modified)?;
map.end()
}
}Manual flattening gives control over the output structure.
use serde::ser::{Serialize, Serializer, SerializeMap, SerializeStruct};
struct Item {
name: String,
value: i32,
}
// Option 1: serialize_map (flexible, dynamic)
impl Serialize for Item {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut map = serializer.serialize_map(Some(2))?;
map.serialize_entry("name", &self.name)?;
map.serialize_entry("value", &self.value)?;
map.end()
}
}
// Option 2: serialize_struct (fixed schema, potentially optimized)
impl Serialize for Item {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut s = serializer.serialize_struct("Item", 2)?;
s.serialize_field("name", &self.name)?;
s.serialize_field("value", &self.value)?;
s.end()
}
}Use serialize_map for dynamic content; serialize_struct for fixed schemas.
use serde::ser::{Serialize, Serializer, SerializeMap};
struct FilteredMap {
entries: Vec<(String, Option<i32>)>,
}
impl Serialize for FilteredMap {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
// Filter entries before serializing
let valid_entries: Vec<_> = self.entries.iter()
.filter(|(_, v)| v.is_some())
.collect();
let mut map = serializer.serialize_map(Some(valid_entries.len()))?;
for (key, value) in valid_entries {
map.serialize_entry(key, value)?;
}
map.end()
}
}Pre-filter entries to ensure accurate length hints.
use serde::ser::{Serialize, Serializer, SerializeMap};
use std::io::Write;
struct LargeDataset {
items: Vec<(String, String)>,
}
impl Serialize for LargeDataset {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
// Stream entries without collecting
let mut map = serializer.serialize_map(Some(self.items.len()))?;
for (key, value) in &self.items {
map.serialize_entry(key, value)?;
// Each entry is written incrementally
}
map.end()
}
}
// For streaming directly to a writer:
fn write_large_map<W: Write>(writer: W, items: &[(String, String)]) -> serde_json::Result<()> {
let mut serializer = serde_json::Serializer::new(writer);
let mut map = serializer.serialize_map(Some(items.len()))?;
for (key, value) in items {
map.serialize_entry(key, value)?;
}
map.end()
}Maps can be streamed to avoid memory overhead for large datasets.
use serde::ser::{Error, SerializeMap, Ok};
use std::fmt;
struct XmlMapState {
elements: Vec<String>,
}
impl SerializeMap for XmlMapState {
type Ok = String;
type Error = fmt::Error;
fn serialize_entry<K, V>(&mut self, key: &K, value: &V) -> Result<(), Self::Error>
where
K: ?Sized + serde::Serialize,
V: ?Sized + serde::Serialize,
{
// For XML, we'd create elements like <key>value</key>
let key_str = format!("{:?}", key); // Simplified
let value_str = format!("{:?}", value); // Simplified
self.elements.push(format!("<{}>{}</{}>", key_str, value_str, key_str));
Ok(())
}
fn serialize_key<K>(&mut self, _key: &K) -> Result<(), Self::Error>
where
K: ?Sized + serde::Serialize,
{
// Handle key separately if needed
Ok(())
}
fn serialize_value<V>(&mut self, _value: &V) -> Result<(), Self::Error>
where
V: ?Sized + serde::Serialize,
{
// Handle value separately if needed
Ok(())
}
fn end(self) -> Result<Self::Ok, Self::Error> {
// Combine all elements into final XML
Ok(format!("<root>{}</root>", self.elements.join("")))
}
}Custom serializers define how end produces final output.
use serde::ser::{Serialize, Serializer, SerializeMap};
struct Container {
items: Vec<String>,
}
impl Serialize for Container {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut map = serializer.serialize_map(Some(1))?;
map.serialize_entry("items", &self.items)?;
// end() returns Result<S::Ok, S::Error>
// S::Ok is typically () for JSON or String for some formats
map.end()
}
}
// The return value of end becomes the return value of serialize
// For most formats, S::Ok is (), meaning serialization writes to output
// For some formats, S::Ok returns the serialized dataThe return type S::Ok depends on the serializer's output format.
use serde::ser::{Serialize, Serializer, SerializeMap, SerializeStruct};
struct Data {
a: i32,
b: String,
}
// Both patterns end similarly but differ in semantics:
impl Serialize for Data {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
// Map: key-value pairs, dynamic structure
let mut map = serializer.serialize_map(Some(2))?;
map.serialize_entry("a", &self.a)?;
map.serialize_entry("b", &self.b)?;
map.end()
}
}
impl Serialize for Data {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
// Struct: named fields, fixed structure
let mut s = serializer.serialize_struct("Data", 2)?;
s.serialize_field("a", &self.a)?;
s.serialize_field("b", &self.b)?;
s.end()
}
}
// Both end() methods finalize their respective structures
// Map is for key-value data, Struct is for typed structuresBoth SerializeMap::end and SerializeStruct::end finalize their structures.
The serialization sequence:
| Step | Method | Purpose |
|------|--------|---------|
| 1 | serializer.serialize_map(len) | Start map, get SerializeMap |
| 2 | map.serialize_entry(k, v) | Add key-value pairs |
| 3 | map.end() | Finalize and return result |
Why end is required:
| Reason | Explanation |
|--------|-------------|
| Closing delimiter | Write closing } for JSON |
| Buffer flush | Ensure all data is written |
| Return result | Produce final output |
| Resource cleanup | Release any held resources |
Key behaviors:
| Behavior | Detail |
|----------|--------|
| Must be called | Skipping end leaves serialization incomplete |
| Returns result | Produces S::Ok which may be () or data |
| Error propagation | Returns Err if any previous operation failed |
| Final operation | No more entries can be added after end |
Common patterns:
// Basic map
let mut map = serializer.serialize_map(Some(len))?;
for (k, v) in items {
map.serialize_entry(k, v)?;
}
map.end()
// With conditional fields
let mut map = serializer.serialize_map(None)?;
map.serialize_entry("required", &self.required)?;
if let Some(ref opt) = self.optional {
map.serialize_entry("optional", opt)?;
}
map.end()
// Flatten nested structure
let mut map = serializer.serialize_map(Some(total_fields))?;
// Add fields from multiple sources
map.serialize_entry("field1", &self.field1)?;
map.serialize_entry("nested.field", &self.nested.field)?;
map.end()Key insight: SerializeMap::end is the final step in map serialization that converts the accumulated key-value pairs into the final output format. It's called after all entries have been added via serialize_entry (or serialize_key/serialize_value), and it returns the serializer's Ok type—typically () for streaming formats like JSON that write directly to an output, or a collected value for formats that buffer their output. The method must be called exactly once per map; omitting it leaves the output incomplete (missing the closing delimiter in JSON), while calling it twice or after adding more entries would panic or produce malformed output. The pattern mirrors SerializeStruct::end but applies to dynamic key-value data rather than fixed-field structures, making it essential for custom Serialize implementations on map-like types, hash maps, or structs that need flexible serialization.