1use 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
17pub struct JsonSerializer {
19 output: String,
20 settings: Arc<JsonCodecSettings>,
21 needs_comma: bool,
23 expecting_map_key: bool,
25 map_depth: usize,
28}
29
30impl JsonSerializer {
31 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 pub fn finish(self) -> Vec<u8> {
44 self.output.into_bytes()
45 }
46
47 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 if self.map_depth > 0 {
64 self.expecting_map_key = true;
65 }
66 }
67
68 fn field_name<'a>(&self, schema: &'a Schema) -> Option<&'a str> {
70 self.settings.member_to_field(schema)
71 }
72
73 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 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 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 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 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 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 self.output.push_str(&formatted);
303 }
304 _ => {
305 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 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 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 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 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 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 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}