How does 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.

Basic Map Serialization Pattern

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.

What end Does Internally

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 output

For JSON, end writes the closing brace and returns the result.

Sequential Key-Value Serialization

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.

Separate Key and Value Serialization

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.

Unknown Length Serialization

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.

Custom Serializer Implementation

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.

Nested Map Serialization

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.

Conditional Entries

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.

Error Handling During Serialization

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 immediately

If serialization fails mid-sequence, end is never reached.

Dynamic Key-Value Pairs

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 objects

Dynamic serialization uses the same pattern with type-erased values.

SerializeMap for Structs

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.

Flattening Nested Structures

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.

Comparison: serialize_map vs serialize_struct

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.

Skipping Entries During Serialization

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.

Streaming Large Maps

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.

Implementing SerializeMap for Custom Formats

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.

Return Value of end

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 data

The return type S::Ok depends on the serializer's output format.

Comparison with SerializeStruct::end

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 structures

Both SerializeMap::end and SerializeStruct::end finalize their structures.

Synthesis

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.