1use std::fmt;
20
21use serde::de::{
22 self, DeserializeOwned, DeserializeSeed, EnumAccess, IntoDeserializer, MapAccess, SeqAccess,
23 VariantAccess, Visitor,
24};
25use serde::forward_to_deserialize_any;
26use serde_json::{Map, Value};
27
28pub fn from_value<T: DeserializeOwned>(value: &Value) -> Result<T, CoerceError> {
30 T::deserialize(Coercing::new(value))
31}
32
33#[derive(Debug, Clone, PartialEq, Eq)]
35pub struct CoerceError {
36 message: String,
37}
38
39impl CoerceError {
40 fn new(message: impl Into<String>) -> Self {
41 Self { message: message.into() }
42 }
43
44 pub fn message(&self) -> &str {
46 &self.message
47 }
48}
49
50impl fmt::Display for CoerceError {
51 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
52 f.write_str(&self.message)
53 }
54}
55
56impl std::error::Error for CoerceError {}
57
58impl de::Error for CoerceError {
59 fn custom<T: fmt::Display>(message: T) -> Self {
60 Self::new(message.to_string())
61 }
62}
63
64pub struct Coercing<'de> {
66 value: &'de Value,
67}
68
69impl<'de> Coercing<'de> {
70 pub fn new(value: &'de Value) -> Self {
72 Self { value }
73 }
74
75 fn kind(&self) -> &'static str {
76 match self.value {
77 Value::Null => "null",
78 Value::Bool(_) => "a boolean",
79 Value::Number(_) => "a number",
80 Value::String(_) => "a string",
81 Value::Array(_) => "an array",
82 Value::Object(_) => "an object",
83 }
84 }
85
86 fn invalid(&self, wanted: &str) -> CoerceError {
87 CoerceError::new(format!("expected {wanted}, found {}", self.kind()))
88 }
89}
90
91macro_rules! coerce_number {
94 ($method:ident, $ty:ty, $visit:ident) => {
95 fn $method<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, CoerceError> {
96 let parsed: $ty = match self.value {
97 Value::String(raw) => raw.trim().parse().map_err(|_| {
98 CoerceError::new(format!(
99 "`{raw}` is not a valid {}",
100 std::any::type_name::<$ty>()
101 ))
102 })?,
103 Value::Number(n) => n.to_string().parse().map_err(|_| {
104 CoerceError::new(format!(
105 "{n} does not fit in a {}",
106 std::any::type_name::<$ty>()
107 ))
108 })?,
109 Value::Bool(b) => {
112 if *b {
113 1 as $ty
114 } else {
115 0 as $ty
116 }
117 }
118 _ => return Err(self.invalid("a number")),
119 };
120 visitor.$visit(parsed)
121 }
122 };
123}
124
125impl<'de> de::Deserializer<'de> for Coercing<'de> {
126 type Error = CoerceError;
127
128 fn deserialize_any<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, CoerceError> {
129 match self.value {
130 Value::Null => visitor.visit_unit(),
131 Value::Bool(b) => visitor.visit_bool(*b),
132 Value::String(s) => visitor.visit_str(s),
133 Value::Number(n) => {
134 if let Some(i) = n.as_i64() {
135 visitor.visit_i64(i)
136 } else if let Some(u) = n.as_u64() {
137 visitor.visit_u64(u)
138 } else if let Some(f) = n.as_f64() {
139 visitor.visit_f64(f)
140 } else {
141 Err(CoerceError::new("unrepresentable number"))
142 }
143 }
144 Value::Array(items) => visitor.visit_seq(SeqReader { items, index: 0 }),
145 Value::Object(map) => visitor.visit_map(MapReader {
146 entries: map.iter().collect(),
147 index: 0,
148 value: None,
149 }),
150 }
151 }
152
153 fn deserialize_bool<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, CoerceError> {
154 let parsed = match self.value {
155 Value::Bool(b) => *b,
156 Value::String(raw) => match raw.trim().to_ascii_lowercase().as_str() {
158 "true" | "1" | "on" | "yes" => true,
159 "false" | "0" | "off" | "no" => false,
160 other => return Err(CoerceError::new(format!("`{other}` is not a valid boolean"))),
161 },
162 Value::Number(n) => n.as_i64().is_some_and(|i| i != 0),
163 _ => return Err(self.invalid("a boolean")),
164 };
165 visitor.visit_bool(parsed)
166 }
167
168 coerce_number!(deserialize_i8, i8, visit_i8);
169 coerce_number!(deserialize_i16, i16, visit_i16);
170 coerce_number!(deserialize_i32, i32, visit_i32);
171 coerce_number!(deserialize_i64, i64, visit_i64);
172 coerce_number!(deserialize_u8, u8, visit_u8);
173 coerce_number!(deserialize_u16, u16, visit_u16);
174 coerce_number!(deserialize_u32, u32, visit_u32);
175 coerce_number!(deserialize_u64, u64, visit_u64);
176 coerce_number!(deserialize_f32, f32, visit_f32);
177 coerce_number!(deserialize_f64, f64, visit_f64);
178
179 fn deserialize_char<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, CoerceError> {
180 match self.value {
181 Value::String(s) if s.chars().count() == 1 => {
182 visitor.visit_char(s.chars().next().expect("just counted one"))
183 }
184 Value::String(_) => Err(CoerceError::new("expected a single character")),
185 _ => Err(self.invalid("a character")),
186 }
187 }
188
189 fn deserialize_str<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, CoerceError> {
190 self.deserialize_string(visitor)
191 }
192
193 fn deserialize_string<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, CoerceError> {
194 match self.value {
195 Value::String(s) => visitor.visit_str(s),
196 Value::Number(n) => visitor.visit_string(n.to_string()),
199 Value::Bool(b) => visitor.visit_string(b.to_string()),
200 _ => Err(self.invalid("a string")),
201 }
202 }
203
204 fn deserialize_bytes<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, CoerceError> {
205 match self.value {
206 Value::String(s) => visitor.visit_bytes(s.as_bytes()),
207 _ => Err(self.invalid("bytes")),
208 }
209 }
210
211 fn deserialize_byte_buf<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, CoerceError> {
212 self.deserialize_bytes(visitor)
213 }
214
215 fn deserialize_option<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, CoerceError> {
216 match self.value {
217 Value::Null => visitor.visit_none(),
218 _ => visitor.visit_some(self),
219 }
220 }
221
222 fn deserialize_unit<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, CoerceError> {
223 match self.value {
224 Value::Null => visitor.visit_unit(),
225 _ => Err(self.invalid("null")),
226 }
227 }
228
229 fn deserialize_unit_struct<V: Visitor<'de>>(
230 self,
231 _name: &'static str,
232 visitor: V,
233 ) -> Result<V::Value, CoerceError> {
234 self.deserialize_unit(visitor)
235 }
236
237 fn deserialize_newtype_struct<V: Visitor<'de>>(
238 self,
239 _name: &'static str,
240 visitor: V,
241 ) -> Result<V::Value, CoerceError> {
242 visitor.visit_newtype_struct(self)
243 }
244
245 fn deserialize_seq<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, CoerceError> {
246 match self.value {
247 Value::Array(items) => visitor.visit_seq(SeqReader { items, index: 0 }),
248 Value::Null => visitor.visit_seq(SeqReader { items: &[], index: 0 }),
252 single => visitor.visit_seq(SingleReader { value: Some(single) }),
253 }
254 }
255
256 fn deserialize_tuple<V: Visitor<'de>>(
257 self,
258 _len: usize,
259 visitor: V,
260 ) -> Result<V::Value, CoerceError> {
261 self.deserialize_seq(visitor)
262 }
263
264 fn deserialize_tuple_struct<V: Visitor<'de>>(
265 self,
266 _name: &'static str,
267 _len: usize,
268 visitor: V,
269 ) -> Result<V::Value, CoerceError> {
270 self.deserialize_seq(visitor)
271 }
272
273 fn deserialize_map<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, CoerceError> {
274 match self.value {
275 Value::Object(map) => visitor.visit_map(MapReader {
276 entries: map.iter().collect(),
277 index: 0,
278 value: None,
279 }),
280 _ => Err(self.invalid("an object")),
281 }
282 }
283
284 fn deserialize_struct<V: Visitor<'de>>(
285 self,
286 _name: &'static str,
287 _fields: &'static [&'static str],
288 visitor: V,
289 ) -> Result<V::Value, CoerceError> {
290 self.deserialize_map(visitor)
291 }
292
293 fn deserialize_enum<V: Visitor<'de>>(
294 self,
295 _name: &'static str,
296 _variants: &'static [&'static str],
297 visitor: V,
298 ) -> Result<V::Value, CoerceError> {
299 match self.value {
300 Value::String(name) => visitor.visit_enum(name.as_str().into_deserializer()),
302 Value::Object(map) if map.len() == 1 => {
304 let (name, value) = map.iter().next().expect("just checked len == 1");
305 visitor.visit_enum(EnumReader { name, value })
306 }
307 _ => Err(self.invalid("an enum variant")),
308 }
309 }
310
311 fn deserialize_identifier<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, CoerceError> {
312 self.deserialize_string(visitor)
313 }
314
315 forward_to_deserialize_any! { ignored_any }
316}
317
318struct SeqReader<'de> {
319 items: &'de [Value],
320 index: usize,
321}
322
323impl<'de> SeqAccess<'de> for SeqReader<'de> {
324 type Error = CoerceError;
325
326 fn next_element_seed<T: DeserializeSeed<'de>>(
327 &mut self,
328 seed: T,
329 ) -> Result<Option<T::Value>, CoerceError> {
330 let Some(value) = self.items.get(self.index) else {
331 return Ok(None);
332 };
333 self.index += 1;
334 seed.deserialize(Coercing::new(value)).map(Some)
335 }
336
337 fn size_hint(&self) -> Option<usize> {
338 Some(self.items.len() - self.index)
339 }
340}
341
342struct SingleReader<'de> {
344 value: Option<&'de Value>,
345}
346
347impl<'de> SeqAccess<'de> for SingleReader<'de> {
348 type Error = CoerceError;
349
350 fn next_element_seed<T: DeserializeSeed<'de>>(
351 &mut self,
352 seed: T,
353 ) -> Result<Option<T::Value>, CoerceError> {
354 match self.value.take() {
355 Some(value) => seed.deserialize(Coercing::new(value)).map(Some),
356 None => Ok(None),
357 }
358 }
359
360 fn size_hint(&self) -> Option<usize> {
361 Some(self.value.is_some() as usize)
362 }
363}
364
365struct MapReader<'de> {
366 entries: Vec<(&'de String, &'de Value)>,
367 index: usize,
368 value: Option<&'de Value>,
369}
370
371impl<'de> MapAccess<'de> for MapReader<'de> {
372 type Error = CoerceError;
373
374 fn next_key_seed<K: DeserializeSeed<'de>>(
375 &mut self,
376 seed: K,
377 ) -> Result<Option<K::Value>, CoerceError> {
378 let Some((key, value)) = self.entries.get(self.index) else {
379 return Ok(None);
380 };
381 self.index += 1;
382 self.value = Some(value);
383 seed.deserialize(key.as_str().into_deserializer()).map(Some)
384 }
385
386 fn next_value_seed<V: DeserializeSeed<'de>>(
387 &mut self,
388 seed: V,
389 ) -> Result<V::Value, CoerceError> {
390 let value =
391 self.value.take().ok_or_else(|| CoerceError::new("value requested before its key"))?;
392 seed.deserialize(Coercing::new(value))
393 }
394
395 fn size_hint(&self) -> Option<usize> {
396 Some(self.entries.len() - self.index)
397 }
398}
399
400struct EnumReader<'de> {
401 name: &'de String,
402 value: &'de Value,
403}
404
405impl<'de> EnumAccess<'de> for EnumReader<'de> {
406 type Error = CoerceError;
407 type Variant = Self;
408
409 fn variant_seed<V: DeserializeSeed<'de>>(
410 self,
411 seed: V,
412 ) -> Result<(V::Value, Self), CoerceError> {
413 let name = seed.deserialize(self.name.as_str().into_deserializer())?;
414 Ok((name, self))
415 }
416}
417
418impl<'de> VariantAccess<'de> for EnumReader<'de> {
419 type Error = CoerceError;
420
421 fn unit_variant(self) -> Result<(), CoerceError> {
422 Ok(())
423 }
424
425 fn newtype_variant_seed<T: DeserializeSeed<'de>>(
426 self,
427 seed: T,
428 ) -> Result<T::Value, CoerceError> {
429 seed.deserialize(Coercing::new(self.value))
430 }
431
432 fn tuple_variant<V: Visitor<'de>>(
433 self,
434 _len: usize,
435 visitor: V,
436 ) -> Result<V::Value, CoerceError> {
437 de::Deserializer::deserialize_seq(Coercing::new(self.value), visitor)
438 }
439
440 fn struct_variant<V: Visitor<'de>>(
441 self,
442 _fields: &'static [&'static str],
443 visitor: V,
444 ) -> Result<V::Value, CoerceError> {
445 de::Deserializer::deserialize_map(Coercing::new(self.value), visitor)
446 }
447}
448
449pub fn object_from_strings<'a>(pairs: impl IntoIterator<Item = (&'a String, &'a String)>) -> Value {
452 let mut map = Map::new();
453 for (key, value) in pairs {
454 map.insert(key.clone(), Value::String(value.clone()));
455 }
456 Value::Object(map)
457}
458
459#[cfg(test)]
460mod tests {
461 use super::*;
462 use serde::Deserialize;
463 use serde_json::json;
464
465 #[derive(Debug, PartialEq, Deserialize)]
466 struct Pagination {
467 page: u32,
468 per_page: Option<u16>,
469 sort: String,
470 }
471
472 #[test]
473 fn coerces_strings_into_numbers() {
474 let parsed: Pagination =
475 from_value(&json!({ "page": "2", "per_page": "50", "sort": "name" })).unwrap();
476 assert_eq!(parsed, Pagination { page: 2, per_page: Some(50), sort: "name".into() });
477 }
478
479 #[test]
480 fn real_numbers_still_work() {
481 let parsed: Pagination =
482 from_value(&json!({ "page": 2, "per_page": 50, "sort": "name" })).unwrap();
483 assert_eq!(parsed.page, 2);
484 }
485
486 #[test]
487 fn numbers_coerce_into_strings() {
488 #[derive(Deserialize)]
489 struct Row {
490 postcode: String,
491 }
492 let parsed: Row = from_value(&json!({ "postcode": 1234 })).unwrap();
493 assert_eq!(parsed.postcode, "1234");
494 }
495
496 #[test]
497 fn coerces_the_booleans_a_form_actually_sends() {
498 #[derive(Deserialize)]
499 struct Flags {
500 a: bool,
501 b: bool,
502 c: bool,
503 d: bool,
504 }
505 let parsed: Flags =
506 from_value(&json!({ "a": "1", "b": "true", "c": "on", "d": "no" })).unwrap();
507 assert!(parsed.a && parsed.b && parsed.c && !parsed.d);
508 }
509
510 #[test]
511 fn a_missing_optional_is_none() {
512 let parsed: Pagination = from_value(&json!({ "page": "1", "sort": "id" })).unwrap();
513 assert_eq!(parsed.per_page, None);
514 }
515
516 #[test]
517 fn an_explicit_null_optional_is_none() {
518 let parsed: Pagination =
519 from_value(&json!({ "page": "1", "per_page": null, "sort": "id" })).unwrap();
520 assert_eq!(parsed.per_page, None);
521 }
522
523 #[test]
524 fn an_empty_string_is_not_silently_treated_as_absent() {
525 let err = from_value::<Pagination>(&json!({ "page": "1", "per_page": "", "sort": "id" }))
527 .unwrap_err();
528 assert!(err.message().contains("not a valid"), "{}", err.message());
529 }
530
531 #[test]
532 fn an_unparsable_number_names_the_value() {
533 let err = from_value::<Pagination>(&json!({ "page": "abc", "sort": "id" })).unwrap_err();
534 assert!(err.message().contains("`abc`"), "{}", err.message());
535 }
536
537 #[test]
538 fn a_single_value_deserialises_as_a_one_element_list() {
539 #[derive(Deserialize)]
540 struct Tags {
541 tag: Vec<String>,
542 }
543 let one: Tags = from_value(&json!({ "tag": "rust" })).unwrap();
544 assert_eq!(one.tag, vec!["rust"]);
545
546 let many: Tags = from_value(&json!({ "tag": ["rust", "web"] })).unwrap();
547 assert_eq!(many.tag, vec!["rust", "web"]);
548 }
549
550 #[test]
551 fn nested_structures_coerce_all_the_way_down() {
552 #[derive(Debug, PartialEq, Deserialize)]
553 struct Inner {
554 n: i64,
555 }
556 #[derive(Debug, PartialEq, Deserialize)]
557 struct Outer {
558 inner: Inner,
559 list: Vec<Inner>,
560 }
561
562 let parsed: Outer =
563 from_value(&json!({ "inner": { "n": "1" }, "list": [{ "n": "2" }, { "n": "3" }] }))
564 .unwrap();
565 assert_eq!(parsed.inner.n, 1);
566 assert_eq!(parsed.list, vec![Inner { n: 2 }, Inner { n: 3 }]);
567 }
568
569 #[test]
570 fn unit_enum_variants_come_from_strings() {
571 #[derive(Debug, PartialEq, Deserialize)]
572 #[serde(rename_all = "lowercase")]
573 enum Status {
574 Active,
575 Archived,
576 }
577 #[derive(Debug, PartialEq, Deserialize)]
578 struct Filter {
579 status: Status,
580 }
581
582 let parsed: Filter = from_value(&json!({ "status": "archived" })).unwrap();
583 assert_eq!(parsed.status, Status::Archived);
584 }
585
586 #[test]
587 fn deserialises_a_bare_scalar() {
588 assert_eq!(from_value::<u64>(&json!("42")).unwrap(), 42);
589 assert_eq!(from_value::<String>(&json!("x")).unwrap(), "x");
590 assert!(from_value::<bool>(&json!("yes")).unwrap());
591 }
592
593 #[test]
594 fn a_wrong_shape_reports_what_it_found() {
595 let err = from_value::<Pagination>(&json!("nope")).unwrap_err();
596 assert!(err.message().contains("found a string"), "{}", err.message());
597 }
598
599 #[test]
600 fn out_of_range_numbers_are_rejected() {
601 assert!(from_value::<u8>(&json!("300")).is_err());
602 assert!(from_value::<i32>(&json!("-1")).is_ok());
603 assert!(from_value::<u32>(&json!("-1")).is_err());
604 }
605
606 #[test]
607 fn builds_an_object_from_string_pairs() {
608 let id = ("id".to_string(), "7".to_string());
609 let slug = ("slug".to_string(), "hello".to_string());
610 let value = object_from_strings([(&id.0, &id.1), (&slug.0, &slug.1)]);
611 assert_eq!(value, json!({ "id": "7", "slug": "hello" }));
612
613 #[derive(Deserialize)]
614 struct Params {
615 id: u64,
616 slug: String,
617 }
618 let parsed: Params = from_value(&value).unwrap();
619 assert_eq!(parsed.id, 7);
620 assert_eq!(parsed.slug, "hello");
621 }
622}