jsn 0.14.0

A library for querying streaming JSON tokens
Documentation
use crate::raw_token::RawToken;
use std::fmt::Display;

/// A JSON token
//
/// You can convert a token to a Rust type using [`Token::get()`].
///
/// ```
/// use jsn::TokenReader;
///
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// let mut iter = TokenReader::new(r#""world""#.as_bytes())
///     .into_iter();
///
/// let token = iter.next().unwrap()?;
/// let s: Option<&str> = token.get();
/// let s: Option<String> = token.get();
/// # Ok(())
/// # }
/// ```
///
/// The [`PartialEq`] trait is implemented for common Rust types to make comparisons
/// easy:
///
/// ```
/// use jsn::{TokenReader};
///
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
///
/// let mut iter = TokenReader::new("123".as_bytes())
///     .into_iter();
/// let token = iter.next().unwrap()?;
/// assert_eq!(token ,123u8);
///
/// # Ok(())
/// # }
///
/// ```
#[derive(Debug, PartialEq, Eq, Clone, Hash)]
pub enum Token {
    /// A null value: ` null`
    Null,
    /// A boolean value: `true`\`false`
    Bool(bool),
    /// A string value (with unicode escapes decoded): `"a json đŸ˜€string "`
    String(String),
    /// An object key.
    ObjectKey(String),
    /// A numeric value of arbitrary precision: `-0.5e1`
    ///
    /// Numbers are stored as a byte buffer representing the ascii digits that make up the number.
    /// No conversion is attempted unless specifically requested with [`Token::get`]
    Number(Vec<u8>),
    /// Start of an object `{`
    ObjectStart,
    /// End of an object `}`
    ObjectEnd,
    /// Start of an array `[`
    ArrayStart,
    /// End of an array `]`
    ArrayEnd,
    /// Value separator `,`
    Comma,
    /// Object key-value separator
    Colon,
}

// Sealed trait pattern
mod private {
    pub trait Sealed {}

    impl Sealed for bool {}
    impl Sealed for String {}
    impl Sealed for &str {}
    impl Sealed for u8 {}
    impl Sealed for u16 {}
    impl Sealed for u32 {}
    impl Sealed for u64 {}
    impl Sealed for usize {}
    impl Sealed for i8 {}
    impl Sealed for i16 {}
    impl Sealed for i32 {}
    impl Sealed for i64 {}
    impl Sealed for isize {}
    impl Sealed for f64 {}
}

/// A trait for types that can be created from a JSON token
///
/// This trait is sealed and may not be implemented outside this crate
pub trait FromJson<'o>: private::Sealed
where
    Self: Sized,
    Self: 'o,
{
    /// Converts the JSON token into a Rust value
    fn from_json(value: &'o Token) -> Option<Self>;
}

impl FromJson<'_> for bool {
    fn from_json(value: &Token) -> Option<Self> {
        match value {
            Token::Bool(b) => Some(*b),
            _ => None,
        }
    }
}

impl FromJson<'_> for String {
    fn from_json(value: &Token) -> Option<Self> {
        match value {
            Token::String(ref s) => Some(s.clone()),
            _ => None,
        }
    }
}

impl<'o> FromJson<'o> for &'o str {
    fn from_json(value: &'o Token) -> Option<Self> {
        match value {
            Token::String(ref s) => Some(s.as_str()),
            _ => None,
        }
    }
}

macro_rules! from_json_number_impl {
    ($e:ty) => {
        impl FromJson<'_> for $e {
            fn from_json(value: &crate::token::Token) -> Option<Self> {
                match value {
                    Token::Number(ref n) => lexical::parse(n).ok(),
                    _ => None,
                }
            }
        }
    };
}

from_json_number_impl!(u8);
from_json_number_impl!(u16);
from_json_number_impl!(u32);
from_json_number_impl!(u64);
from_json_number_impl!(usize);
from_json_number_impl!(i8);
from_json_number_impl!(i16);
from_json_number_impl!(i32);
from_json_number_impl!(i64);
from_json_number_impl!(isize);
from_json_number_impl!(f64);

impl PartialEq<&str> for Token {
    fn eq(&self, other: &&str) -> bool {
        match self {
            Token::String(ref s) => s == other,
            Token::ObjectKey(ref s) => s == other,
            _ => false,
        }
    }
}

impl PartialEq<String> for Token {
    fn eq(&self, other: &String) -> bool {
        match self {
            Token::String(ref s) => s == other,
            Token::ObjectKey(ref s) => s == other,
            _ => false,
        }
    }
}

impl PartialEq<bool> for Token {
    fn eq(&self, other: &bool) -> bool {
        match self {
            Token::Bool(ref b) => b == other,
            _ => false,
        }
    }
}

macro_rules! partialeq_number_impl {
    ($e:ty) => {
        impl PartialEq<$e> for Token {
            fn eq(&self, other: &$e) -> bool {
                match &self {
                    Token::Number(_) => self.get::<$e>().map(|n| &n == other).unwrap_or(false),
                    _ => false,
                }
            }
        }
    };
}

partialeq_number_impl!(u8);
partialeq_number_impl!(u16);
partialeq_number_impl!(u32);
partialeq_number_impl!(u64);
partialeq_number_impl!(usize);
partialeq_number_impl!(i8);
partialeq_number_impl!(i16);
partialeq_number_impl!(i32);
partialeq_number_impl!(i64);
partialeq_number_impl!(isize);
partialeq_number_impl!(f64);

impl Token {
    /// Attempts to convert the token to a value of type `T`
    ///
    /// This method works for any type that implements the [`FromJson`] trait. This trait is
    /// implemented for common Rust types
    ///
    /// # Examples
    ///
    /// ```
    /// use jsn::{TokenReader};
    ///
    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
    /// let mut iter = TokenReader::new("12.329824".as_bytes()).into_iter();
    /// let token = iter.next().unwrap()?;
    ///
    /// // The type to convert to can be inferred
    /// let num: f64 = token.get().unwrap();
    /// assert_eq!(num, 12.329824);
    ///
    /// let mut iter = TokenReader::new("false".as_bytes()).into_iter();
    /// let token = iter.next().unwrap()?;
    ///
    /// // But you can also specify a type using the turbofish operator
    /// assert_eq!(token.get::<bool>(), Some(false));
    ///
    /// let mut iter = TokenReader::new(r#""hello""#.as_bytes()).into_iter();
    /// let token = iter.next().unwrap()?;
    ///
    /// // You can convert to to both owned and borrowed strings.
    /// let s: String = token.get().unwrap();
    /// assert_eq!(s, "hello");
    ///
    /// let s: &str = token.get().unwrap();
    /// assert_eq!(s, "hello");
    ///
    /// # Ok(())
    /// # }
    /// ```
    pub fn get<'o, T: FromJson<'o>>(&'o self) -> Option<T> {
        T::from_json(self)
    }

    /// Returns `true` if this token is an object key
    pub fn is_object_key(&self) -> bool {
        matches!(self, Token::ObjectKey(_))
    }

    /// Returns `true` if this token is a JSON string
    pub fn is_string(&self) -> bool {
        matches!(self, Token::String(_))
    }

    /// Returns `true` if this token is a JSON boolean
    pub fn is_bool(&self) -> bool {
        matches!(self, Token::Bool(_))
    }

    /// Returns `true` if this token is a JSON number
    pub fn is_number(&self) -> bool {
        matches!(self, Token::Number(_))
    }
}

impl From<RawToken<'_>> for Token {
    fn from(value: RawToken<'_>) -> Self {
        match value {
            RawToken::Eof => panic!("EOF should cause the iterator to return None"),
            RawToken::ArrayStart => Token::ArrayStart,
            RawToken::ArrayEnd => Token::ArrayEnd,
            RawToken::ObjectStart => Token::ObjectStart,
            RawToken::ObjectEnd => Token::ObjectEnd,
            RawToken::Colon => Token::Colon,
            RawToken::Comma => Token::Comma,
            RawToken::ObjectKey(s) => Token::ObjectKey(String::from(s)),
            RawToken::Number(n) => Token::Number(n.to_vec()),
            RawToken::String(s) => Token::String(String::from(s)),
            RawToken::Bool(b) => Token::Bool(b),
            RawToken::Null => Token::Null,
        }
    }
}

impl Display for Token {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Token::Null => write!(f, "null"),
            Token::ArrayStart => write!(f, "["),
            Token::ObjectStart => write!(f, "{{"),
            Token::ArrayEnd => write!(f, "]"),
            Token::ObjectEnd => write!(f, "}}"),
            Token::Comma => write!(f, ","),
            Token::Colon => write!(f, ":"),
            Token::Number(n) => write!(
                f,
                "{}",
                std::str::from_utf8(n).expect("numbers should be utf8")
            ),
            Token::String(s) | Token::ObjectKey(s) => write!(f, "\"{}\"", s),
            Token::Bool(true) => write!(f, "true"),
            Token::Bool(false) => write!(f, "false"),
        }
    }
}

#[cfg(test)]
mod token_tests {
    use super::*;

    #[test]
    fn token_equality() {
        // Number
        assert_eq!(Token::Number(b"12".to_vec()), 12u8);
        // String
        assert_eq!(Token::String("hello".into()), "hello");
        // Boolean
        assert_eq!(Token::Bool(true), true);
    }

    #[test]
    fn token_conversion() {
        // Numbers
        assert_eq!(Some(12u8), Token::Number(b"12".to_vec()).get());

        assert_eq!(Some(-12i8), Token::Number(b"-12".to_vec()).get());
        assert_eq!(Some(300u16), Token::Number(b"300".to_vec()).get());
        assert_eq!(Some(-300i16), Token::Number(b"-300".to_vec()).get());
        assert_eq!(Some(70_000u32), Token::Number(b"70000".to_vec()).get());
        assert_eq!(Some(-70_000i32), Token::Number(b"-70000".to_vec()).get());
        assert_eq!(
            Some(5_000_000_000u64),
            Token::Number(b"5000000000".to_vec()).get()
        );
        assert_eq!(
            Some(-5_000_000_000i64),
            Token::Number(b"-5000000000".to_vec()).get()
        );
        assert_eq!(Some(3.134), Token::Number(b"3.134".to_vec()).get());
        assert_eq!(Some(3.134), Token::Number(b"0.3134e1".to_vec()).get());
        assert_eq!(Some(3.134), Token::Number(b"3134e-3".to_vec()).get());

        // bool
        assert_eq!(Some(true), Token::Bool(true).get());
        assert_eq!(Some(false), Token::Bool(false).get());

        // String
        assert_eq!(
            Some(String::from("hello")),
            Token::String(String::from("hello")).get()
        );
        assert_eq!(Some("hello"), Token::String(String::from("hello")).get());
    }
}