Skip to main content

blazingly_json/
number.rs

1use serde::{Deserialize, Serialize};
2use std::fmt;
3
4/// A finite JSON number represented without losing signed or unsigned integers.
5#[derive(Clone, Copy, Debug)]
6pub struct Number(Repr);
7
8#[derive(Clone, Copy, Debug)]
9enum Repr {
10    I64(i64),
11    U64(u64),
12    F64(f64),
13}
14
15impl Number {
16    /// Creates a JSON number from a finite float.
17    #[must_use]
18    pub fn from_f64(value: f64) -> Option<Self> {
19        value.is_finite().then_some(Self(Repr::F64(value)))
20    }
21
22    #[must_use]
23    pub fn is_i64(&self) -> bool {
24        self.as_i64().is_some()
25    }
26
27    #[must_use]
28    pub const fn is_u64(&self) -> bool {
29        matches!(self.0, Repr::U64(_))
30    }
31
32    #[must_use]
33    pub const fn is_f64(&self) -> bool {
34        matches!(self.0, Repr::F64(_))
35    }
36
37    #[must_use]
38    pub fn as_i64(&self) -> Option<i64> {
39        match self.0 {
40            Repr::I64(value) => Some(value),
41            Repr::U64(value) => i64::try_from(value).ok(),
42            Repr::F64(_) => None,
43        }
44    }
45
46    #[must_use]
47    pub fn as_u64(&self) -> Option<u64> {
48        match self.0 {
49            Repr::I64(value) => u64::try_from(value).ok(),
50            Repr::U64(value) => Some(value),
51            Repr::F64(_) => None,
52        }
53    }
54
55    #[must_use]
56    #[allow(clippy::cast_precision_loss)]
57    pub fn as_f64(&self) -> Option<f64> {
58        match self.0 {
59            Repr::I64(value) => Some(value as f64),
60            Repr::U64(value) => Some(value as f64),
61            Repr::F64(value) => Some(value),
62        }
63    }
64}
65
66impl PartialEq for Number {
67    fn eq(&self, other: &Self) -> bool {
68        match (self.0, other.0) {
69            (Repr::I64(left), Repr::I64(right)) => left == right,
70            (Repr::U64(left), Repr::U64(right)) => left == right,
71            (Repr::F64(left), Repr::F64(right)) => left == right,
72            _ => false,
73        }
74    }
75}
76
77impl Eq for Number {}
78
79impl fmt::Display for Number {
80    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
81        match self.0 {
82            Repr::I64(value) => value.fmt(formatter),
83            Repr::U64(value) => value.fmt(formatter),
84            Repr::F64(value) => formatter.write_str(zmij::Buffer::new().format_finite(value)),
85        }
86    }
87}
88
89impl Serialize for Number {
90    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
91        match self.0 {
92            Repr::I64(value) => serializer.serialize_i64(value),
93            Repr::U64(value) => serializer.serialize_u64(value),
94            Repr::F64(value) => serializer.serialize_f64(value),
95        }
96    }
97}
98
99impl<'de> Deserialize<'de> for Number {
100    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
101        struct NumberVisitor;
102
103        impl serde::de::Visitor<'_> for NumberVisitor {
104            type Value = Number;
105
106            fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
107                formatter.write_str("a JSON number")
108            }
109
110            fn visit_i64<E>(self, value: i64) -> Result<Self::Value, E> {
111                Ok(Number::from(value))
112            }
113
114            fn visit_u64<E>(self, value: u64) -> Result<Self::Value, E> {
115                Ok(Number::from(value))
116            }
117
118            fn visit_f64<E: serde::de::Error>(self, value: f64) -> Result<Self::Value, E> {
119                Number::from_f64(value).ok_or_else(|| E::custom("non-finite JSON number"))
120            }
121        }
122
123        deserializer.deserialize_any(NumberVisitor)
124    }
125}
126
127macro_rules! from_signed {
128    ($($type:ty),+ $(,)?) => {
129        $(
130            impl From<$type> for Number {
131                fn from(value: $type) -> Self {
132                    let value = i64::try_from(value).expect("signed primitive fits in i64");
133                    if value < 0 {
134                        Self(Repr::I64(value))
135                    } else {
136                        Self(Repr::U64(
137                            u64::try_from(value).expect("nonnegative i64 fits in u64"),
138                        ))
139                    }
140                }
141            }
142        )+
143    };
144}
145
146macro_rules! from_unsigned {
147    ($($type:ty),+ $(,)?) => {
148        $(
149            impl From<$type> for Number {
150                fn from(value: $type) -> Self {
151                    Self(Repr::U64(u64::try_from(value).expect("unsigned primitive fits in u64")))
152                }
153            }
154        )+
155    };
156}
157
158from_signed!(i8, i16, i32, i64, isize);
159from_unsigned!(u8, u16, u32, u64, usize);