aws_smithy_json/codec/
serializer.rs

1/*
2 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 * SPDX-License-Identifier: Apache-2.0
4 */
5
6//! JSON serializer implementation.
7
8use aws_smithy_schema::serde::{SerdeError, SerializableStruct, ShapeSerializer};
9use aws_smithy_schema::Schema;
10use aws_smithy_types::date_time::Format as TimestampFormat;
11use aws_smithy_types::{BigDecimal, BigInteger, Blob, DateTime, Document};
12
13use crate::codec::JsonCodecSettings;
14
15use std::sync::Arc;
16
17/// JSON serializer that implements the ShapeSerializer trait.
18pub struct JsonSerializer {
19    output: String,
20    settings: Arc<JsonCodecSettings>,
21    // Tracks whether a comma is needed before the next value in the current container.
22    needs_comma: bool,
23    // When true, the next write_string is a map key — emit "key": instead of ,"key"
24    expecting_map_key: bool,
25    // Nesting depth of write_map calls. When >0, prefix() restores expecting_map_key
26    // after each value write so the next write_string is treated as a key.
27    map_depth: usize,
28}
29
30impl JsonSerializer {
31    /// Creates a new JSON serializer with the given settings.
32    pub(crate) fn new(settings: Arc<JsonCodecSettings>) -> Self {
33        Self {
34            output: String::new(),
35            settings,
36            needs_comma: false,
37            expecting_map_key: false,
38            map_depth: 0,
39        }
40    }
41
42    /// Finalizes the serialization and returns the output as bytes.
43    pub fn finish(self) -> Vec<u8> {
44        self.output.into_bytes()
45    }
46
47    /// Handles comma separators and member names before writing a value.
48    /// When inside a map (map_depth > 0), restores expecting_map_key after
49    /// the value so the next write_string is treated as a map key.
50    fn prefix(&mut self, schema: &Schema) {
51        if self.needs_comma {
52            self.output.push(',');
53        }
54        if let Some(name) = self.field_name(schema) {
55            self.output.push('"');
56            self.output.push_str(&crate::escape::escape_string(name));
57            self.output.push_str("\":");
58        }
59        self.needs_comma = true;
60        // Inside a map, after writing a value the next write_string should be a key.
61        // This is safe because write_string checks expecting_map_key *before* calling
62        // prefix(), so this only affects the *next* write_string call.
63        if self.map_depth > 0 {
64            self.expecting_map_key = true;
65        }
66    }
67
68    /// Resolves the JSON field name for a member schema.
69    fn field_name<'a>(&self, schema: &'a Schema) -> Option<&'a str> {
70        self.settings.member_to_field(schema)
71    }
72
73    /// Gets the timestamp format to use, respecting @timestampFormat trait.
74    fn get_timestamp_format(&self, schema: &Schema) -> TimestampFormat {
75        if let Some(ts_trait) = schema.timestamp_format() {
76            return match ts_trait.format() {
77                aws_smithy_schema::traits::TimestampFormat::EpochSeconds => {
78                    TimestampFormat::EpochSeconds
79                }
80                aws_smithy_schema::traits::TimestampFormat::HttpDate => TimestampFormat::HttpDate,
81                aws_smithy_schema::traits::TimestampFormat::DateTime => TimestampFormat::DateTime,
82            };
83        }
84        self.settings.default_timestamp_format
85    }
86
87    fn write_json_value(&mut self, doc: &Document) {
88        use crate::serialize::JsonValueWriter;
89        let writer = JsonValueWriter::new(&mut self.output);
90        writer.document(doc);
91    }
92}
93
94impl aws_smithy_schema::codec::FinishSerializer for JsonSerializer {
95    fn finish(self) -> Vec<u8> {
96        self.output.into_bytes()
97    }
98}
99
100impl ShapeSerializer for JsonSerializer {
101    fn write_struct(
102        &mut self,
103        schema: &Schema,
104        value: &dyn SerializableStruct,
105    ) -> Result<(), SerdeError> {
106        self.prefix(schema);
107        self.output.push('{');
108        let saved_comma = self.needs_comma;
109        let saved_depth = self.map_depth;
110        let saved_map_key = self.expecting_map_key;
111        self.needs_comma = false;
112        // Reset map state so struct members don't trigger map-key logic.
113        // Restored after the struct body so an enclosing map resumes correctly.
114        self.map_depth = 0;
115        self.expecting_map_key = false;
116        value.serialize_members(self)?;
117        self.output.push('}');
118        self.needs_comma = saved_comma;
119        self.map_depth = saved_depth;
120        self.expecting_map_key = saved_map_key;
121        Ok(())
122    }
123
124    fn write_list(
125        &mut self,
126        schema: &Schema,
127        write_elements: &dyn Fn(&mut dyn ShapeSerializer) -> Result<(), SerdeError>,
128    ) -> Result<(), SerdeError> {
129        self.prefix(schema);
130        self.output.push('[');
131        let saved = self.needs_comma;
132        let saved_depth = self.map_depth;
133        let saved_map_key = self.expecting_map_key;
134        self.needs_comma = false;
135        // Reset map state so list elements don't trigger map-key logic in prefix().
136        self.map_depth = 0;
137        self.expecting_map_key = false;
138        write_elements(self)?;
139        self.output.push(']');
140        self.needs_comma = saved;
141        self.map_depth = saved_depth;
142        self.expecting_map_key = saved_map_key;
143        Ok(())
144    }
145
146    fn write_map(
147        &mut self,
148        schema: &Schema,
149        write_entries: &dyn Fn(&mut dyn ShapeSerializer) -> Result<(), SerdeError>,
150    ) -> Result<(), SerdeError> {
151        self.prefix(schema);
152        self.output.push('{');
153        let saved_comma = self.needs_comma;
154        let saved_map_key = self.expecting_map_key;
155        let saved_depth = self.map_depth;
156        self.needs_comma = false;
157        self.expecting_map_key = true;
158        // Increment depth so prefix() knows to restore expecting_map_key after
159        // each value write. write_string checks expecting_map_key *before* calling
160        // prefix(), so the flag only affects the *next* write_string (the next key).
161        self.map_depth += 1;
162        write_entries(self)?;
163        self.map_depth = saved_depth;
164        self.output.push('}');
165        self.needs_comma = saved_comma;
166        self.expecting_map_key = saved_map_key;
167        Ok(())
168    }
169
170    fn write_boolean(&mut self, schema: &Schema, value: bool) -> Result<(), SerdeError> {
171        self.prefix(schema);
172        self.output.push_str(if value { "true" } else { "false" });
173        Ok(())
174    }
175
176    fn write_byte(&mut self, schema: &Schema, value: i8) -> Result<(), SerdeError> {
177        use std::fmt::Write;
178        self.prefix(schema);
179        write!(&mut self.output, "{}", value).map_err(|e| SerdeError::WriteFailed {
180            message: e.to_string(),
181        })
182    }
183
184    fn write_short(&mut self, schema: &Schema, value: i16) -> Result<(), SerdeError> {
185        use std::fmt::Write;
186        self.prefix(schema);
187        write!(&mut self.output, "{}", value).map_err(|e| SerdeError::WriteFailed {
188            message: e.to_string(),
189        })
190    }
191
192    fn write_integer(&mut self, schema: &Schema, value: i32) -> Result<(), SerdeError> {
193        use std::fmt::Write;
194        self.prefix(schema);
195        write!(&mut self.output, "{}", value).map_err(|e| SerdeError::WriteFailed {
196            message: e.to_string(),
197        })
198    }
199
200    fn write_long(&mut self, schema: &Schema, value: i64) -> Result<(), SerdeError> {
201        use std::fmt::Write;
202        self.prefix(schema);
203        write!(&mut self.output, "{}", value).map_err(|e| SerdeError::WriteFailed {
204            message: e.to_string(),
205        })
206    }
207
208    fn write_float(&mut self, schema: &Schema, value: f32) -> Result<(), SerdeError> {
209        use std::fmt::Write;
210        self.prefix(schema);
211        if value.is_nan() {
212            self.output.push_str("\"NaN\"");
213            Ok(())
214        } else if value.is_infinite() {
215            if value.is_sign_positive() {
216                self.output.push_str("\"Infinity\"");
217            } else {
218                self.output.push_str("\"-Infinity\"");
219            }
220            Ok(())
221        } else {
222            write!(&mut self.output, "{}", value).map_err(|e| SerdeError::WriteFailed {
223                message: e.to_string(),
224            })
225        }
226    }
227
228    fn write_double(&mut self, schema: &Schema, value: f64) -> Result<(), SerdeError> {
229        use std::fmt::Write;
230        self.prefix(schema);
231        if value.is_nan() {
232            self.output.push_str("\"NaN\"");
233            Ok(())
234        } else if value.is_infinite() {
235            if value.is_sign_positive() {
236                self.output.push_str("\"Infinity\"");
237            } else {
238                self.output.push_str("\"-Infinity\"");
239            }
240            Ok(())
241        } else {
242            write!(&mut self.output, "{}", value).map_err(|e| SerdeError::WriteFailed {
243                message: e.to_string(),
244            })
245        }
246    }
247
248    fn write_big_integer(&mut self, schema: &Schema, value: &BigInteger) -> Result<(), SerdeError> {
249        self.prefix(schema);
250        self.output.push_str(value.as_ref());
251        Ok(())
252    }
253
254    fn write_big_decimal(&mut self, schema: &Schema, value: &BigDecimal) -> Result<(), SerdeError> {
255        self.prefix(schema);
256        self.output.push_str(value.as_ref());
257        Ok(())
258    }
259
260    fn write_string(&mut self, schema: &Schema, value: &str) -> Result<(), SerdeError> {
261        use crate::escape::escape_string;
262        if self.expecting_map_key {
263            // Map key: comma before (if not first entry), then "key":
264            if self.needs_comma {
265                self.output.push(',');
266            }
267            self.output.push('"');
268            self.output.push_str(&escape_string(value));
269            self.output.push_str("\":");
270            // The next write is the value — no comma before it
271            self.needs_comma = false;
272            self.expecting_map_key = false;
273        } else {
274            self.prefix(schema);
275            self.output.push('"');
276            self.output.push_str(&escape_string(value));
277            self.output.push('"');
278        }
279        Ok(())
280    }
281
282    fn write_blob(&mut self, schema: &Schema, value: &Blob) -> Result<(), SerdeError> {
283        use aws_smithy_types::base64;
284        self.prefix(schema);
285        let encoded = base64::encode(value.as_ref());
286        self.output.push('"');
287        self.output.push_str(&encoded);
288        self.output.push('"');
289        Ok(())
290    }
291
292    fn write_timestamp(&mut self, schema: &Schema, value: &DateTime) -> Result<(), SerdeError> {
293        self.prefix(schema);
294        let format = self.get_timestamp_format(schema);
295        let formatted = value.fmt(format).map_err(|e| SerdeError::WriteFailed {
296            message: format!("failed to format timestamp: {e}"),
297        })?;
298
299        match format {
300            TimestampFormat::EpochSeconds => {
301                // Epoch seconds as number
302                self.output.push_str(&formatted);
303            }
304            _ => {
305                // Other formats as strings
306                self.output.push('"');
307                self.output.push_str(&formatted);
308                self.output.push('"');
309            }
310        }
311        Ok(())
312    }
313
314    fn write_document(&mut self, schema: &Schema, value: &Document) -> Result<(), SerdeError> {
315        self.prefix(schema);
316        self.write_json_value(value);
317        Ok(())
318    }
319
320    fn write_null(&mut self, schema: &Schema) -> Result<(), SerdeError> {
321        self.prefix(schema);
322        self.output.push_str("null");
323        Ok(())
324    }
325}
326
327#[cfg(test)]
328mod tests {
329    use super::*;
330    use aws_smithy_schema::prelude::*;
331    use aws_smithy_schema::ShapeType;
332
333    #[test]
334    fn test_write_boolean() {
335        let mut ser = JsonSerializer::new(Arc::new(JsonCodecSettings::default()));
336        ser.write_boolean(&BOOLEAN, true).unwrap();
337        let output = ser.finish();
338        assert_eq!(String::from_utf8(output).unwrap(), "true");
339    }
340
341    #[test]
342    fn test_write_string() {
343        let mut ser = JsonSerializer::new(Arc::new(JsonCodecSettings::default()));
344        ser.write_string(&STRING, "hello").unwrap();
345        let output = ser.finish();
346        assert_eq!(String::from_utf8(output).unwrap(), "\"hello\"");
347    }
348
349    #[test]
350    fn test_write_integer() {
351        let mut ser = JsonSerializer::new(Arc::new(JsonCodecSettings::default()));
352        ser.write_integer(&INTEGER, 42).unwrap();
353        let output = ser.finish();
354        assert_eq!(String::from_utf8(output).unwrap(), "42");
355    }
356
357    #[test]
358    fn test_write_null() {
359        let mut ser = JsonSerializer::new(Arc::new(JsonCodecSettings::default()));
360        ser.write_null(&STRING).unwrap();
361        let output = ser.finish();
362        assert_eq!(String::from_utf8(output).unwrap(), "null");
363    }
364
365    #[test]
366    fn test_write_list() {
367        let mut ser = JsonSerializer::new(Arc::new(JsonCodecSettings::default()));
368        let list_schema = aws_smithy_schema::Schema::new(
369            aws_smithy_schema::shape_id!("test", "List"),
370            aws_smithy_schema::ShapeType::List,
371        );
372        ser.write_list(&list_schema, &|s: &mut dyn ShapeSerializer| {
373            s.write_integer(&INTEGER, 1)?;
374            s.write_integer(&INTEGER, 2)?;
375            s.write_integer(&INTEGER, 3)?;
376            Ok(())
377        })
378        .unwrap();
379        let output = String::from_utf8(ser.finish()).unwrap();
380        assert_eq!(output, "[1,2,3]");
381    }
382
383    #[test]
384    fn test_write_full_object() {
385        use aws_smithy_schema::serde::SerializableStruct;
386
387        static ACTIVE_MEMBER: Schema = Schema::new_member(
388            aws_smithy_schema::shape_id!("test", "Struct"),
389            aws_smithy_schema::ShapeType::Boolean,
390            "active",
391            0,
392        );
393        static NAME_MEMBER: Schema = Schema::new_member(
394            aws_smithy_schema::shape_id!("test", "Struct"),
395            aws_smithy_schema::ShapeType::String,
396            "name",
397            1,
398        );
399        static COUNT_MEMBER: Schema = Schema::new_member(
400            aws_smithy_schema::shape_id!("test", "Struct"),
401            aws_smithy_schema::ShapeType::Integer,
402            "count",
403            2,
404        );
405        static PRICE_MEMBER: Schema = Schema::new_member(
406            aws_smithy_schema::shape_id!("test", "Struct"),
407            aws_smithy_schema::ShapeType::Float,
408            "price",
409            3,
410        );
411        static ITEMS_MEMBER: Schema = Schema::new_member(
412            aws_smithy_schema::shape_id!("test", "Struct"),
413            aws_smithy_schema::ShapeType::List,
414            "items",
415            4,
416        );
417
418        struct TestObject;
419        impl SerializableStruct for TestObject {
420            fn serialize_members(&self, s: &mut dyn ShapeSerializer) -> Result<(), SerdeError> {
421                s.write_boolean(&ACTIVE_MEMBER, true)?;
422                s.write_string(&NAME_MEMBER, "test")?;
423                s.write_integer(&COUNT_MEMBER, 42)?;
424                s.write_float(&PRICE_MEMBER, 3.15)?;
425                s.write_list(&ITEMS_MEMBER, &|s| {
426                    s.write_integer(&INTEGER, 1)?;
427                    s.write_integer(&INTEGER, 2)?;
428                    Ok(())
429                })?;
430                Ok(())
431            }
432        }
433
434        let struct_schema = aws_smithy_schema::Schema::new(
435            aws_smithy_schema::shape_id!("test", "Struct"),
436            aws_smithy_schema::ShapeType::Structure,
437        );
438        let mut ser = JsonSerializer::new(Arc::new(JsonCodecSettings::default()));
439        ser.write_struct(&struct_schema, &TestObject).unwrap();
440        let output = String::from_utf8(ser.finish()).unwrap();
441        assert_eq!(
442            output,
443            r#"{"active":true,"name":"test","count":42,"price":3.15,"items":[1,2]}"#
444        );
445    }
446
447    #[test]
448    fn test_nested_complex_serialization() {
449        use aws_smithy_schema::serde::SerializableStruct;
450
451        // Member schemas
452        static ID: Schema = Schema::new_member(
453            aws_smithy_schema::shape_id!("test", "User"),
454            aws_smithy_schema::ShapeType::Long,
455            "id",
456            0,
457        );
458        static NAME: Schema = Schema::new_member(
459            aws_smithy_schema::shape_id!("test", "User"),
460            aws_smithy_schema::ShapeType::String,
461            "name",
462            1,
463        );
464        static SCORES: Schema = Schema::new_member(
465            aws_smithy_schema::shape_id!("test", "User"),
466            aws_smithy_schema::ShapeType::List,
467            "scores",
468            2,
469        );
470        static ADDRESS: Schema = Schema::new_member(
471            aws_smithy_schema::shape_id!("test", "User"),
472            aws_smithy_schema::ShapeType::Structure,
473            "address",
474            3,
475        );
476        static COMPANIES: Schema = Schema::new_member(
477            aws_smithy_schema::shape_id!("test", "User"),
478            aws_smithy_schema::ShapeType::List,
479            "companies",
480            4,
481        );
482        static TAGS: Schema = Schema::new_member(
483            aws_smithy_schema::shape_id!("test", "User"),
484            aws_smithy_schema::ShapeType::Map,
485            "tags",
486            5,
487        );
488        static STREET: Schema = Schema::new_member(
489            aws_smithy_schema::shape_id!("test", "Address"),
490            aws_smithy_schema::ShapeType::String,
491            "street",
492            0,
493        );
494        static CITY: Schema = Schema::new_member(
495            aws_smithy_schema::shape_id!("test", "Address"),
496            aws_smithy_schema::ShapeType::String,
497            "city",
498            1,
499        );
500        static ZIP: Schema = Schema::new_member(
501            aws_smithy_schema::shape_id!("test", "Address"),
502            aws_smithy_schema::ShapeType::Integer,
503            "zip",
504            2,
505        );
506        static COMP_NAME: Schema = Schema::new_member(
507            aws_smithy_schema::shape_id!("test", "Company"),
508            aws_smithy_schema::ShapeType::String,
509            "name",
510            0,
511        );
512        static EMPLOYEES: Schema = Schema::new_member(
513            aws_smithy_schema::shape_id!("test", "Company"),
514            aws_smithy_schema::ShapeType::List,
515            "employees",
516            1,
517        );
518        static METADATA: Schema = Schema::new_member(
519            aws_smithy_schema::shape_id!("test", "Company"),
520            aws_smithy_schema::ShapeType::Map,
521            "metadata",
522            2,
523        );
524        static ACTIVE: Schema = Schema::new_member(
525            aws_smithy_schema::shape_id!("test", "Company"),
526            aws_smithy_schema::ShapeType::Boolean,
527            "active",
528            3,
529        );
530
531        struct AddressStruct;
532        impl SerializableStruct for AddressStruct {
533            fn serialize_members(&self, s: &mut dyn ShapeSerializer) -> Result<(), SerdeError> {
534                s.write_string(&STREET, "123 Main St")?;
535                s.write_string(&CITY, "Seattle")?;
536                s.write_integer(&ZIP, 98101)?;
537                Ok(())
538            }
539        }
540
541        struct CompanyStruct {
542            name: &'static str,
543            employees: &'static [&'static str],
544            metadata: &'static [(&'static str, i32)],
545            active: bool,
546        }
547        impl SerializableStruct for CompanyStruct {
548            fn serialize_members(&self, s: &mut dyn ShapeSerializer) -> Result<(), SerdeError> {
549                s.write_string(&COMP_NAME, self.name)?;
550                s.write_list(&EMPLOYEES, &|s| {
551                    for e in self.employees {
552                        s.write_string(&STRING, e)?;
553                    }
554                    Ok(())
555                })?;
556                s.write_map(&METADATA, &|s| {
557                    for (k, v) in self.metadata {
558                        s.write_string(&::aws_smithy_schema::prelude::STRING, k)?;
559                        s.write_integer(&::aws_smithy_schema::prelude::INTEGER, *v)?;
560                    }
561                    Ok(())
562                })?;
563                s.write_boolean(&ACTIVE, self.active)?;
564                Ok(())
565            }
566        }
567
568        struct UserStruct;
569        impl SerializableStruct for UserStruct {
570            fn serialize_members(&self, s: &mut dyn ShapeSerializer) -> Result<(), SerdeError> {
571                s.write_long(&ID, 12345)?;
572                s.write_string(&NAME, "John Doe")?;
573                s.write_list(&SCORES, &|s| {
574                    s.write_double(&DOUBLE, 95.5)?;
575                    s.write_double(&DOUBLE, 87.3)?;
576                    s.write_double(&DOUBLE, 92.1)?;
577                    Ok(())
578                })?;
579                s.write_struct(&ADDRESS, &AddressStruct)?;
580                s.write_list(&COMPANIES, &|s| {
581                    let struct_schema = Schema::new(
582                        aws_smithy_schema::shape_id!("test", "Company"),
583                        aws_smithy_schema::ShapeType::Structure,
584                    );
585                    s.write_struct(
586                        &struct_schema,
587                        &CompanyStruct {
588                            name: "TechCorp",
589                            employees: &["Alice", "Bob"],
590                            metadata: &[("founded", 2010), ("size", 500)],
591                            active: true,
592                        },
593                    )?;
594                    s.write_struct(
595                        &struct_schema,
596                        &CompanyStruct {
597                            name: "StartupInc",
598                            employees: &["Charlie"],
599                            metadata: &[("founded", 2020)],
600                            active: false,
601                        },
602                    )?;
603                    Ok(())
604                })?;
605                s.write_map(&TAGS, &|s| {
606                    s.write_string(&::aws_smithy_schema::prelude::STRING, "role")?;
607                    s.write_string(&::aws_smithy_schema::prelude::STRING, "admin")?;
608                    s.write_string(&::aws_smithy_schema::prelude::STRING, "level")?;
609                    s.write_string(&::aws_smithy_schema::prelude::STRING, "senior")?;
610                    Ok(())
611                })?;
612                Ok(())
613            }
614        }
615
616        let struct_schema = Schema::new(
617            aws_smithy_schema::shape_id!("test", "User"),
618            aws_smithy_schema::ShapeType::Structure,
619        );
620        let mut ser = JsonSerializer::new(Arc::new(JsonCodecSettings::default()));
621        ser.write_struct(&struct_schema, &UserStruct).unwrap();
622        let output = ser.finish();
623        // Expected compact JSON (br# avoids escape noise)
624        let expected: &[u8] = br#"{"id":12345,"name":"John Doe","scores":[95.5,87.3,92.1],"address":{"street":"123 Main St","city":"Seattle","zip":98101},"companies":[{"name":"TechCorp","employees":["Alice","Bob"],"metadata":{"founded":2010,"size":500},"active":true},{"name":"StartupInc","employees":["Charlie"],"metadata":{"founded":2020},"active":false}],"tags":{"role":"admin","level":"senior"}}"#;
625        assert_eq!(output, expected);
626    }
627
628    #[test]
629    fn test_json_name_serialization() {
630        use aws_smithy_schema::serde::SerializableStruct;
631
632        static FOO_MEMBER: Schema = Schema::new_member(
633            aws_smithy_schema::shape_id!("test", "MyStruct"),
634            aws_smithy_schema::ShapeType::String,
635            "foo",
636            0,
637        );
638        // bar has @jsonName("Baz")
639        static BAR_MEMBER: Schema = Schema::new_member(
640            aws_smithy_schema::shape_id!("test", "MyStruct"),
641            aws_smithy_schema::ShapeType::Integer,
642            "bar",
643            1,
644        )
645        .with_json_name("Baz");
646
647        struct TestStruct;
648        impl SerializableStruct for TestStruct {
649            fn serialize_members(&self, s: &mut dyn ShapeSerializer) -> Result<(), SerdeError> {
650                s.write_string(&FOO_MEMBER, "hello")?;
651                s.write_integer(&BAR_MEMBER, 42)?;
652                Ok(())
653            }
654        }
655
656        // With use_json_name=true (default), "bar" should serialize as "Baz"
657        let struct_schema = Schema::new(
658            aws_smithy_schema::shape_id!("test", "MyStruct"),
659            aws_smithy_schema::ShapeType::Structure,
660        );
661        let mut ser = JsonSerializer::new(Arc::new(JsonCodecSettings::default()));
662        ser.write_struct(&struct_schema, &TestStruct).unwrap();
663        let output = String::from_utf8(ser.finish()).unwrap();
664        assert_eq!(output, r#"{"foo":"hello","Baz":42}"#);
665
666        // With use_json_name=false, "bar" should stay as "bar"
667        let mut ser = JsonSerializer::new(Arc::new(
668            JsonCodecSettings::builder().use_json_name(false).build(),
669        ));
670        ser.write_struct(&struct_schema, &TestStruct).unwrap();
671        let output = String::from_utf8(ser.finish()).unwrap();
672        assert_eq!(output, r#"{"foo":"hello","bar":42}"#);
673    }
674
675    #[test]
676    fn struct_inside_map_serializes_member_names_correctly() {
677        // Regression test: when a struct is a map value, the map's expecting_map_key
678        // flag must not leak into the struct's member serialization.
679        use aws_smithy_schema::serde::{SerializableStruct, ShapeSerializer};
680
681        static INNER_NAME: Schema = Schema::new_member(
682            aws_smithy_schema::shape_id!("test", "Inner"),
683            ShapeType::String,
684            "name",
685            0,
686        );
687        static INNER_MEMBERS: &[&Schema] = &[&INNER_NAME];
688        static INNER_SCHEMA: Schema = Schema::new_struct(
689            aws_smithy_schema::shape_id!("test", "Inner"),
690            ShapeType::Structure,
691            INNER_MEMBERS,
692        );
693
694        struct Inner;
695        impl SerializableStruct for Inner {
696            fn serialize_members(
697                &self,
698                ser: &mut dyn ShapeSerializer,
699            ) -> Result<(), aws_smithy_schema::serde::SerdeError> {
700                ser.write_string(&INNER_NAME, "Alice")
701            }
702        }
703
704        static MAP_SCHEMA: Schema = Schema::new(
705            aws_smithy_schema::shape_id!("test", "MyMap"),
706            ShapeType::Map,
707        );
708
709        let mut ser = JsonSerializer::new(Arc::new(JsonCodecSettings::default()));
710        ser.write_map(&MAP_SCHEMA, &|ser| {
711            ser.write_string(&aws_smithy_schema::prelude::STRING, "key1")?;
712            ser.write_struct(&INNER_SCHEMA, &Inner)?;
713            Ok(())
714        })
715        .unwrap();
716        let output = String::from_utf8(ser.finish()).unwrap();
717        assert_eq!(output, r#"{"key1":{"name":"Alice"}}"#);
718    }
719}