use serde::de::{self, Visitor};
use serde::{Deserialize, Deserializer, Serialize, Serializer};
#[derive(Clone, Debug)]
pub enum Number {
Int(isize),
UInt(usize),
Float(f64),
}
impl Eq for Number {}
impl PartialEq for Number {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(Self::Int(left), Self::Int(right)) => left == right,
(Self::UInt(left), Self::UInt(right)) => left == right,
(Self::Float(left), Self::Float(right)) => left == right,
_ => false,
}
}
}
impl Serialize for Number {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
match self {
Self::Int(value) => serializer.serialize_i64(*value as i64),
Self::UInt(value) => serializer.serialize_u64(*value as u64),
Self::Float(value) => {
if value.fract() == 0.0 && value.is_finite() {
if *value < 0.0 {
serializer.serialize_i64(*value as i64)
} else {
serializer.serialize_u64(*value as u64)
}
} else {
serializer.serialize_f64(*value)
}
}
}
}
}
struct NumberVisitor;
impl<'de> Visitor<'de> for NumberVisitor {
type Value = Number;
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
formatter.write_str("a number (integer or float)")
}
fn visit_i64<E>(self, v: i64) -> Result<Self::Value, E>
where
E: de::Error,
{
Ok(Number::Int(v as isize))
}
fn visit_u64<E>(self, v: u64) -> Result<Self::Value, E>
where
E: de::Error,
{
Ok(Number::UInt(v as usize))
}
fn visit_f64<E>(self, v: f64) -> Result<Self::Value, E>
where
E: de::Error,
{
Ok(Number::Float(v))
}
}
impl<'de> Deserialize<'de> for Number {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
deserializer.deserialize_any(NumberVisitor)
}
}
macro_rules! impl_from_for_number {
( $( $ty:ident => $pat:ident $( as $as:ident )? ),* ) => {
$(
impl From<$ty> for Number {
fn from(value: $ty) -> Self {
Self::$pat(value $( as $as )?)
}
}
)*
};
}
#[rustfmt::skip]
impl_from_for_number!(
f32 => Float as f64, f64 => Float,
i8 => Int as isize, i16 => Int as isize, i32 => Int as isize, i64 => Int as isize,
u8 => UInt as usize, u16 => UInt as usize, u32 => UInt as usize, u64 => UInt as usize,
isize => Int, usize => UInt
);
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_serialize_int() {
let n = Number::Int(42);
assert_eq!(serde_json::to_string(&n).unwrap(), "42");
}
#[test]
fn test_serialize_negative_int() {
let n = Number::Int(-5);
assert_eq!(serde_json::to_string(&n).unwrap(), "-5");
}
#[test]
fn test_serialize_uint() {
let n = Number::UInt(100);
assert_eq!(serde_json::to_string(&n).unwrap(), "100");
}
#[test]
#[allow(clippy::approx_constant)]
fn test_serialize_float() {
let n = Number::Float(3.14);
assert_eq!(serde_json::to_string(&n).unwrap(), "3.14");
}
#[test]
fn test_serialize_whole_float_as_integer() {
let n = Number::Float(10.0);
assert_eq!(serde_json::to_string(&n).unwrap(), "10");
}
#[test]
fn test_serialize_negative_whole_float() {
let n = Number::Float(-3.0);
assert_eq!(serde_json::to_string(&n).unwrap(), "-3");
}
#[test]
fn test_from_i32() {
let n: Number = 42i32.into();
assert_eq!(n, Number::Int(42));
}
#[test]
fn test_from_u64() {
let n: Number = 100u64.into();
assert_eq!(n, Number::UInt(100));
}
#[test]
fn test_from_f64() {
let n: Number = 2.5f64.into();
assert_eq!(n, Number::Float(2.5));
}
#[test]
fn test_deserialize_int() {
let n: Number = serde_json::from_str("42").unwrap();
assert_eq!(n, Number::UInt(42));
}
#[test]
fn test_deserialize_negative_int() {
let n: Number = serde_json::from_str("-5").unwrap();
assert_eq!(n, Number::Int(-5));
}
#[test]
#[allow(clippy::approx_constant)]
fn test_deserialize_float() {
let n: Number = serde_json::from_str("3.14").unwrap();
assert_eq!(n, Number::Float(3.14));
}
#[test]
fn test_equality() {
assert_eq!(Number::Int(1), Number::Int(1));
assert_eq!(Number::UInt(1), Number::UInt(1));
assert_eq!(Number::Float(1.5), Number::Float(1.5));
assert_ne!(Number::Int(1), Number::UInt(1));
}
}