1use alloc::string::{String, ToString};
9use alloc::vec::Vec;
10
11use crate::error::{JsonDecodeError, JsonFromStrError};
12use crate::parse::parse_str;
13use crate::value::JsonValue;
14
15pub trait JsonDecode: Sized {
19 fn from_json_value(value: &JsonValue) -> Result<Self, JsonDecodeError>;
21}
22
23pub fn from_json_str<T: JsonDecode>(input: &str) -> Result<T, JsonFromStrError> {
25 let value = parse_str(input)?;
26 let decoded = T::from_json_value(&value)?;
27 Ok(decoded)
28}
29
30macro_rules! impl_int_decode {
31 ($($t:ty),* $(,)?) => {$(
32 impl JsonDecode for $t {
33 fn from_json_value(value: &JsonValue) -> Result<Self, JsonDecodeError> {
34 let number = value
35 .as_number()
36 .ok_or_else(|| JsonDecodeError::unexpected_type("expected a JSON number"))?;
37 number.as_str().parse::<$t>().map_err(|_| {
40 JsonDecodeError::number(
41 "number is not a plain integer that fits the target type",
42 )
43 })
44 }
45 }
46 )*};
47}
48impl_int_decode!(u8, u16, u32, u64, u128, i8, i16, i32, i64, i128);
49
50impl JsonDecode for bool {
51 fn from_json_value(value: &JsonValue) -> Result<Self, JsonDecodeError> {
52 value
53 .as_bool()
54 .ok_or_else(|| JsonDecodeError::unexpected_type("expected a JSON boolean"))
55 }
56}
57
58impl JsonDecode for String {
59 fn from_json_value(value: &JsonValue) -> Result<Self, JsonDecodeError> {
60 value
61 .as_str()
62 .map(ToString::to_string)
63 .ok_or_else(|| JsonDecodeError::unexpected_type("expected a JSON string"))
64 }
65}
66
67impl<T: JsonDecode> JsonDecode for Option<T> {
68 fn from_json_value(value: &JsonValue) -> Result<Self, JsonDecodeError> {
69 if value.is_null() {
70 Ok(None)
71 } else {
72 T::from_json_value(value).map(Some)
73 }
74 }
75}
76
77impl<T: JsonDecode> JsonDecode for Vec<T> {
78 fn from_json_value(value: &JsonValue) -> Result<Self, JsonDecodeError> {
79 let array = value
80 .as_array()
81 .ok_or_else(|| JsonDecodeError::unexpected_type("expected a JSON array"))?;
82 let mut out = Vec::with_capacity(array.len());
83 for item in array {
84 out.push(T::from_json_value(item)?);
85 }
86 Ok(out)
87 }
88}
89
90#[cfg(test)]
91mod tests {
92 use super::*;
93 use crate::encode::{to_json_string, JsonEncode};
94 use crate::error::JsonDecodeErrorKind;
95
96 fn decode<T: JsonDecode>(input: &str) -> T {
97 from_json_str(input).expect("should decode")
98 }
99
100 fn roundtrip<T: JsonEncode + JsonDecode + PartialEq + core::fmt::Debug>(value: T) {
101 let text = to_json_string(&value);
102 let back: T = from_json_str(&text).expect("round-trip should decode");
103 assert_eq!(back, value, "round-trip mismatch for {text}");
104 }
105
106 #[test]
107 fn decodes_scalars() {
108 assert_eq!(decode::<u8>("255"), 255);
109 assert_eq!(decode::<i32>("-5"), -5);
110 assert_eq!(
111 decode::<u128>("340282366920938463463374607431768211455"),
112 u128::MAX
113 );
114 assert!(decode::<bool>("true"));
115 assert_eq!(decode::<String>("\"hi\""), "hi");
116 }
117
118 #[test]
119 fn decodes_option_and_sequences() {
120 assert_eq!(decode::<Option<u8>>("null"), None);
121 assert_eq!(decode::<Option<u8>>("7"), Some(7));
122 assert_eq!(decode::<Vec<u8>>("[1,2,3]"), vec![1, 2, 3]);
123 assert_eq!(decode::<Vec<u8>>("[]"), Vec::<u8>::new());
124 }
125
126 #[test]
127 fn round_trips() {
128 roundtrip(255u8);
129 roundtrip(-12345i32);
130 roundtrip(u128::MAX);
131 roundtrip(true);
132 roundtrip(String::from("hello"));
133 roundtrip(Some(9u16));
134 roundtrip(Option::<u16>::None);
135 roundtrip(vec![1u8, 2, 3]);
136 }
137
138 #[test]
139 fn wrong_type_is_rejected() {
140 let err = from_json_str::<u8>("\"x\"").unwrap_err();
141 match err {
142 JsonFromStrError::Decode(e) => {
143 assert_eq!(e.kind(), JsonDecodeErrorKind::UnexpectedType)
144 }
145 other => panic!("expected decode error, got {other:?}"),
146 }
147 }
148
149 #[test]
150 fn out_of_range_number_is_rejected() {
151 let err = from_json_str::<u8>("256").unwrap_err();
152 match err {
153 JsonFromStrError::Decode(e) => assert_eq!(e.kind(), JsonDecodeErrorKind::Number),
154 other => panic!("expected decode error, got {other:?}"),
155 }
156 }
157
158 #[test]
159 fn non_integer_number_is_rejected() {
160 let err = from_json_str::<u8>("25.0").unwrap_err();
162 assert!(matches!(err, JsonFromStrError::Decode(_)));
163 }
164
165 #[test]
166 fn invalid_json_is_a_parse_error() {
167 let err = from_json_str::<u8>("nope").unwrap_err();
168 assert!(matches!(err, JsonFromStrError::Parse(_)));
169 }
170
171 #[test]
172 fn error_messages_are_readable() {
173 use crate::error::JsonDecodeError;
174 use alloc::string::ToString;
175
176 let error = JsonDecodeError::number("bad number");
177 assert_eq!(error.message(), "bad number");
178 assert_eq!(error.to_string(), "bad number");
179
180 assert!(JsonFromStrError::Decode(error)
182 .to_string()
183 .contains("bad number"));
184 assert!(from_json_str::<u8>("nope")
185 .unwrap_err()
186 .to_string()
187 .contains("invalid JSON"));
188 }
189}