serde: Fix int_or_string_to_int deserialization helper
It's unclear why the previous method didn't work, but this one definitely does.
This commit is contained in:
parent
d220ab0fbe
commit
22ab60128a
@ -1,11 +1,8 @@
|
|||||||
use std::{
|
use std::{collections::BTreeMap, convert::TryInto, fmt};
|
||||||
collections::BTreeMap,
|
|
||||||
convert::{TryFrom, TryInto},
|
|
||||||
};
|
|
||||||
|
|
||||||
use js_int::Int;
|
use js_int::Int;
|
||||||
use serde::{
|
use serde::{
|
||||||
de::{Deserializer, Error as _, IntoDeserializer as _},
|
de::{self, Deserializer, IntoDeserializer as _, Visitor},
|
||||||
Deserialize,
|
Deserialize,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -33,25 +30,6 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper type for deserialize_int_or_string_to_int
|
|
||||||
#[derive(Deserialize)]
|
|
||||||
#[serde(untagged)]
|
|
||||||
enum IntOrString<'a> {
|
|
||||||
Num(Int),
|
|
||||||
Str(&'a str),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TryFrom<IntOrString<'_>> for Int {
|
|
||||||
type Error = js_int::ParseIntError;
|
|
||||||
|
|
||||||
fn try_from(input: IntOrString) -> Result<Self, Self::Error> {
|
|
||||||
match input {
|
|
||||||
IntOrString::Num(n) => Ok(n),
|
|
||||||
IntOrString::Str(string) => string.parse(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Take either an integer number or a string and deserialize to an integer number.
|
/// Take either an integer number or a string and deserialize to an integer number.
|
||||||
///
|
///
|
||||||
/// To be used like this:
|
/// To be used like this:
|
||||||
@ -60,7 +38,61 @@ pub fn int_or_string_to_int<'de, D>(de: D) -> Result<Int, D::Error>
|
|||||||
where
|
where
|
||||||
D: Deserializer<'de>,
|
D: Deserializer<'de>,
|
||||||
{
|
{
|
||||||
IntOrString::deserialize(de)?.try_into().map_err(D::Error::custom)
|
struct IntOrStringVisitor;
|
||||||
|
|
||||||
|
impl<'de> Visitor<'de> for IntOrStringVisitor {
|
||||||
|
type Value = Int;
|
||||||
|
|
||||||
|
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
formatter.write_str("an integer or a string")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_i8<E: de::Error>(self, v: i8) -> Result<Self::Value, E> {
|
||||||
|
Ok(v.into())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_i16<E: de::Error>(self, v: i16) -> Result<Self::Value, E> {
|
||||||
|
Ok(v.into())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_i32<E: de::Error>(self, v: i32) -> Result<Self::Value, E> {
|
||||||
|
Ok(v.into())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_i64<E: de::Error>(self, v: i64) -> Result<Self::Value, E> {
|
||||||
|
v.try_into().map_err(E::custom)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_i128<E: de::Error>(self, v: i128) -> Result<Self::Value, E> {
|
||||||
|
v.try_into().map_err(E::custom)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_u8<E: de::Error>(self, v: u8) -> Result<Self::Value, E> {
|
||||||
|
Ok(v.into())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_u16<E: de::Error>(self, v: u16) -> Result<Self::Value, E> {
|
||||||
|
Ok(v.into())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_u32<E: de::Error>(self, v: u32) -> Result<Self::Value, E> {
|
||||||
|
Ok(v.into())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_u64<E: de::Error>(self, v: u64) -> Result<Self::Value, E> {
|
||||||
|
v.try_into().map_err(E::custom)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_u128<E: de::Error>(self, v: u128) -> Result<Self::Value, E> {
|
||||||
|
v.try_into().map_err(E::custom)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_str<E: de::Error>(self, v: &str) -> Result<Self::Value, E> {
|
||||||
|
v.parse().map_err(E::custom)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
de.deserialize_any(IntOrStringVisitor)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Take a BTreeMap with values of either an integer number or a string and deserialize
|
/// Take a BTreeMap with values of either an integer number or a string and deserialize
|
||||||
@ -73,8 +105,42 @@ where
|
|||||||
D: Deserializer<'de>,
|
D: Deserializer<'de>,
|
||||||
T: Deserialize<'de> + Ord,
|
T: Deserialize<'de> + Ord,
|
||||||
{
|
{
|
||||||
BTreeMap::<T, IntOrString>::deserialize(de)?
|
#[repr(transparent)]
|
||||||
.into_iter()
|
struct IntWrap(Int);
|
||||||
.map(|(k, v)| v.try_into().map(|n| (k, n)).map_err(D::Error::custom))
|
|
||||||
.collect()
|
impl<'de> Deserialize<'de> for IntWrap {
|
||||||
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||||
|
where
|
||||||
|
D: Deserializer<'de>,
|
||||||
|
{
|
||||||
|
int_or_string_to_int(deserializer).map(IntWrap)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(BTreeMap::<T, IntWrap>::deserialize(de)?.into_iter().map(|(k, IntWrap(v))| (k, v)).collect())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use js_int::{int, Int};
|
||||||
|
use matches::assert_matches;
|
||||||
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
use super::int_or_string_to_int;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn int_or_string() -> Result<(), serde_json::Error> {
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
struct Test {
|
||||||
|
#[serde(deserialize_with = "int_or_string_to_int")]
|
||||||
|
num: Int,
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_matches!(
|
||||||
|
serde_json::from_value::<Test>(serde_json::json!({ "num": "0" }))?,
|
||||||
|
Test { num } if num == int!(0)
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user