1 1 | /*
|
2 2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
3 3 | * SPDX-License-Identifier: Apache-2.0
|
4 4 | */
|
5 5 |
|
6 6 | //! JSON codec implementation for schema-based serialization.
|
7 7 |
|
8 8 | use aws_smithy_schema::codec::Codec;
|
9 9 | use aws_smithy_schema::Schema;
|
10 10 | use aws_smithy_types::date_time::Format as TimestampFormat;
|
11 - | use std::collections::HashMap;
|
12 - | use std::sync::Mutex;
|
13 - |
|
14 11 | use std::sync::Arc;
|
15 12 |
|
16 13 | mod deserializer;
|
17 14 | mod serializer;
|
18 15 |
|
19 16 | pub use deserializer::JsonDeserializer;
|
20 17 | pub use serializer::JsonSerializer;
|
21 18 |
|
22 19 | /// Maps between Smithy member names and JSON wire field names.
|
23 20 | ///
|
24 21 | /// When `@jsonName` is enabled, the wire name may differ from the member name.
|
25 22 | /// This type handles the mapping in both directions and caches the reverse
|
26 23 | /// lookup (wire name → member index) per struct schema.
|
27 24 | #[derive(Debug)]
|
28 25 | enum JsonFieldMapper {
|
29 26 | /// Uses member names directly, ignoring `@jsonName`.
|
30 27 | UseMemberName,
|
31 - | /// Uses `@jsonName` trait values when present, with a cached reverse map.
|
32 - | UseJsonName {
|
33 - | /// Cache from schema pointer → (wire name → member index).
|
34 - | cache: Mutex<HashMap<usize, HashMap<String, usize>>>,
|
35 - | },
|
28 + | /// Uses `@jsonName` trait values when present, falling back to member name.
|
29 + | UseJsonName,
|
36 30 | }
|
37 31 |
|
38 32 | impl JsonFieldMapper {
|
39 33 | /// Returns the JSON wire name for a member schema.
|
40 34 | fn member_to_field<'a>(&self, member: &'a Schema) -> Option<&'a str> {
|
41 35 | let name = member.member_name()?;
|
42 36 | match self {
|
43 37 | JsonFieldMapper::UseMemberName => Some(name),
|
44 - | JsonFieldMapper::UseJsonName { .. } => {
|
38 + | JsonFieldMapper::UseJsonName => {
|
45 39 | if let Some(jn) = member.json_name() {
|
46 40 | return Some(jn.value());
|
47 41 | }
|
48 42 | Some(name)
|
49 43 | }
|
50 44 | }
|
51 45 | }
|
52 46 |
|
53 47 | /// Resolves a JSON wire field name to a member schema within a struct schema.
|
54 48 | fn field_to_member<'s>(&self, schema: &'s Schema, field_name: &str) -> Option<&'s Schema> {
|
55 49 | match self {
|
56 50 | JsonFieldMapper::UseMemberName => schema.member_schema(field_name),
|
57 - | JsonFieldMapper::UseJsonName { cache } => {
|
58 - | let key = std::ptr::from_ref(schema) as usize;
|
59 - | let mut cache = cache.lock().unwrap();
|
60 - | let map = cache.entry(key).or_insert_with(|| {
|
61 - | let mut map = HashMap::new();
|
62 - | for (idx, member) in schema.members().iter().enumerate() {
|
63 - | if let Some(jn) = member.json_name() {
|
64 - | map.insert(jn.value().to_string(), idx);
|
51 + | JsonFieldMapper::UseJsonName => {
|
52 + | // Check @jsonName on each member. For typical struct sizes
|
53 + | // (< 50 members), linear scan is faster than a cached HashMap
|
54 + | // behind a Mutex.
|
55 + | for member in schema.members() {
|
56 + | if let Some(jn) = member.json_name() {
|
57 + | if jn.value() == field_name {
|
58 + | return Some(member);
|
65 59 | }
|
60 + | } else if member.member_name() == Some(field_name) {
|
61 + | return Some(member);
|
66 62 | }
|
67 - | map
|
68 - | });
|
69 - | if let Some(&idx) = map.get(field_name) {
|
70 - | return schema.member_schema_by_index(idx);
|
71 63 | }
|
72 - | schema.member_schema(field_name)
|
64 + | None
|
73 65 | }
|
74 66 | }
|
75 67 | }
|
76 68 | }
|
77 69 |
|
78 70 | /// Configuration for JSON codec behavior.
|
79 71 | ///
|
80 72 | /// Use the builder methods to construct settings:
|
81 73 | /// ```
|
82 74 | /// use aws_smithy_json::codec::JsonCodecSettings;
|
83 75 | ///
|
84 76 | /// let settings = JsonCodecSettings::builder()
|
85 77 | /// .use_json_name(false)
|
86 78 | /// .build();
|
87 79 | /// ```
|
88 80 | #[derive(Debug)]
|
89 81 | pub struct JsonCodecSettings {
|
90 82 | field_mapper: JsonFieldMapper,
|
91 83 | default_timestamp_format: TimestampFormat,
|
92 84 | }
|
93 85 |
|
94 86 | impl JsonCodecSettings {
|
95 87 | /// Creates a builder for `JsonCodecSettings`.
|
96 88 | pub fn builder() -> JsonCodecSettingsBuilder {
|
97 89 | JsonCodecSettingsBuilder::default()
|
98 90 | }
|
99 91 |
|
100 92 | /// Default timestamp format when not specified by `@timestampFormat` trait.
|
101 93 | pub fn default_timestamp_format(&self) -> TimestampFormat {
|
102 94 | self.default_timestamp_format
|
103 95 | }
|
104 96 |
|
105 97 | /// Returns the JSON wire name for a member schema.
|
106 98 | pub(crate) fn member_to_field<'a>(&self, member: &'a Schema) -> Option<&'a str> {
|
107 99 | self.field_mapper.member_to_field(member)
|
108 100 | }
|
109 101 |
|
110 102 | /// Resolves a JSON wire field name to a member schema.
|
111 103 | pub(crate) fn field_to_member<'s>(
|
112 104 | &self,
|
113 105 | schema: &'s Schema,
|
114 106 | field_name: &str,
|
115 107 | ) -> Option<&'s Schema> {
|
116 108 | self.field_mapper.field_to_member(schema, field_name)
|
117 109 | }
|
118 110 | }
|
119 111 |
|
120 112 | impl Default for JsonCodecSettings {
|
121 113 | fn default() -> Self {
|
122 114 | Self {
|
123 - | field_mapper: JsonFieldMapper::UseJsonName {
|
124 - | cache: Mutex::new(HashMap::new()),
|
125 - | },
|
115 + | field_mapper: JsonFieldMapper::UseJsonName,
|
126 116 | default_timestamp_format: TimestampFormat::EpochSeconds,
|
127 117 | }
|
128 118 | }
|
129 119 | }
|
130 120 |
|
131 121 | /// Builder for [`JsonCodecSettings`].
|
132 122 | #[derive(Debug, Clone)]
|
133 123 | pub struct JsonCodecSettingsBuilder {
|
134 124 | use_json_name: bool,
|
135 125 | default_timestamp_format: TimestampFormat,
|
136 126 | }
|
137 127 |
|
138 128 | impl Default for JsonCodecSettingsBuilder {
|
139 129 | fn default() -> Self {
|
140 130 | Self {
|
141 131 | use_json_name: true,
|
142 132 | default_timestamp_format: TimestampFormat::EpochSeconds,
|
143 133 | }
|
144 134 | }
|
145 135 | }
|
146 136 |
|
147 137 | impl JsonCodecSettingsBuilder {
|
148 138 | /// Whether to use the `@jsonName` trait for member names.
|
149 139 | pub fn use_json_name(mut self, value: bool) -> Self {
|
150 140 | self.use_json_name = value;
|
151 141 | self
|
152 142 | }
|
153 143 |
|
154 144 | /// Default timestamp format when not specified by `@timestampFormat` trait.
|
155 145 | pub fn default_timestamp_format(mut self, value: TimestampFormat) -> Self {
|
156 146 | self.default_timestamp_format = value;
|
157 147 | self
|
158 148 | }
|
159 149 |
|
160 150 | /// Builds the settings.
|
161 151 | pub fn build(self) -> JsonCodecSettings {
|
162 152 | let field_mapper = if self.use_json_name {
|
163 - | JsonFieldMapper::UseJsonName {
|
164 - | cache: Mutex::new(HashMap::new()),
|
165 - | }
|
153 + | JsonFieldMapper::UseJsonName
|
166 154 | } else {
|
167 155 | JsonFieldMapper::UseMemberName
|
168 156 | };
|
169 157 | JsonCodecSettings {
|
170 158 | field_mapper,
|
171 159 | default_timestamp_format: self.default_timestamp_format,
|
172 160 | }
|
173 161 | }
|
174 162 | }
|
175 163 |
|