[go: up one dir, main page]

sonic-rs 0.3.3

Sonic-rs is a fast Rust JSON library based on SIMD
Documentation
//! Serde between JSON text and Rust data structure.

mod de;
pub(crate) mod number;
pub(crate) mod rawnumber;
mod ser;

pub(crate) use self::de::tri;
pub use self::{
    de::{from_slice, from_slice_unchecked, from_str, Deserializer},
    number::{JsonNumberTrait, Number},
    rawnumber::RawNumber,
    ser::{
        to_string, to_string_pretty, to_vec, to_vec_pretty, to_writer, to_writer_pretty, Serializer,
    },
};

#[cfg(test)]
#[allow(clippy::mutable_key_type)]
mod test {
    use std::{borrow::Cow, collections::HashMap, hash::Hash, marker::PhantomData};

    use faststr::FastStr;
    use serde::{Deserialize, Serialize};

    use super::*;
    use crate::Result;

    #[derive(Debug, Deserialize, Serialize, PartialEq)]
    struct Foo {
        name: FastStr,
        id: u64,
    }

    #[derive(Debug, Deserialize, Serialize, Hash, Eq, PartialEq)]
    enum Enum {
        Zero = 0,
        
        Two = 2,
    }

    #[derive(Debug, Deserialize, Serialize, PartialEq)]
    enum FieldEnum {
        Integer(i8),
        Tuple((FastStr, i32)),
        Struct(Foo),
        Unit,
    }

    #[derive(Debug, Deserialize, Serialize, PartialEq)]
    enum FieldlessEnum {
        Tuple(),
        Struct {},
        Unit,
    }

    #[derive(Debug, Deserialize, Serialize, PartialEq)]
    struct Wrapper<'a>(&'a str);

    // A unit struct
    #[derive(Debug, Deserialize, Serialize, PartialEq)]
    struct Unit;

    // A uint struct
    #[derive(Debug, Deserialize, Serialize, PartialEq)]
    struct Phan<T> {
        phan: String,
        _data: PhantomData<T>,
    }

    // A tuple struct
    #[derive(Debug, Deserialize, Serialize, PartialEq)]
    struct Pair(i32, f32);

    #[derive(Debug, Deserialize, Serialize, PartialEq)]
    struct TestData<'a> {
        fieldless: FieldlessEnum,
        enummap: HashMap<Enum, FieldlessEnum>,
        nummap: HashMap<i64, FieldlessEnum>,
        enum_: Enum,

        // basic types
        boolean: bool,
        integer: i32,
        float: f64,
        int128: i128,
        uint128: u128,
        char_: char,
        // string or bytes
        str_: &'a str,
        // bytes_: &'a [u8],
        string: String,
        faststr: FastStr,
        #[serde(borrow)]
        cow: Cow<'a, str>,
        // containers
        vector: Vec<u32>,
        array: [u32; 1],
        empty_array: [u8; 0],
        map: HashMap<FastStr, f64>,
        map_opkey: HashMap<Option<FastStr>, f64>,

        // enum types
        option: Option<String>,
        fieldenum: FieldEnum,

        // tuple or struct
        tuple: (u64, String),
        tuple_struct: Pair,
        unit_struct: Unit,

        #[serde(borrow)]
        wrapper: Wrapper<'a>,
        phan_struct: Phan<&'a ()>,
    }

    #[test]
    fn test_serde_struct() {
        let data = TestData {
            boolean: true,
            integer: -42,
            float: 3.33,
            int128: -22_000_000_000_000_000_000_000_000,
            uint128: 11_000_000_000_000_000_000_000_000,
            char_: 'A',
            str_: "hello world",
            // bytes_: &[0x52, 0x75, 0x73, 0x74],
            string: String::from("hello world"),
            faststr: FastStr::from("hello world"),
            cow: Cow::Borrowed("borrowed"),
            vector: vec![42, 24, 7],
            array: [99],
            empty_array: [],
            map: {
                let mut m = HashMap::new();
                m.insert(FastStr::from("key1"), 1.1);
                m.insert(FastStr::from("key2"), 2.2);
                m
            },
            map_opkey: {
                #[allow(clippy::mutable_key_type)]
                let mut m = HashMap::new();
                m.insert(Some(FastStr::from("key1")), 1.1);
                m
            },

            option: Some(String::from("I'm here")),
            enummap: {
                let mut m = HashMap::new();
                m.insert(Enum::Zero, FieldlessEnum::Struct {});
                m.insert(Enum::One, FieldlessEnum::Unit);
                m
            },
            nummap: {
                let mut m = HashMap::new();
                m.insert(0, FieldlessEnum::Struct {});
                m.insert(1, FieldlessEnum::Unit);
                m
            },
            fieldenum: FieldEnum::Tuple((FastStr::from("test"), 42)),
            fieldless: FieldlessEnum::Struct {},
            enum_: Enum::One,

            tuple: (42, String::from("test")),
            tuple_struct: Pair(42, 3.33),
            unit_struct: Unit,
            wrapper: Wrapper("hello"),
            phan_struct: Phan {
                phan: String::from("test data"),
                _data: PhantomData,
            },
        };

        let expect = serde_json::to_string(&data).expect("Failed to serialize the data");
        let got = to_string(&data).expect("Failed to serialize the data");
        assert_eq!(expect, got);
        println!("serialized json is {}", got);

        let expect_value: TestData =
            serde_json::from_str(&expect).expect("Failed to deserialize the data");
        let got_value: TestData = from_str(&expect).expect("Failed to deserialize the data");
        assert_eq!(expect_value, got_value);
    }

    #[test]
    fn test_struct_with_skipped() {
        let json = r#"{"unknown":0,"unknown":null,"unknown":1234e123,"unknown":1.234,"unknown":[],"unknown":{},"unknown":{"a":[]},"unknown":[1,2,3],"fieldless":{"Struct":{}},"enummap":{"Zero":{"Struct":{}},"One":"Unit"},"nummap":{"0":{"Struct":{}},"1":"Unit"},"enum_":"One","boolean":true,"integer":-42,"float":3.33,"int128":-22000000000000000000000000,"uint128":11000000000000000000000000,"char_":"A","str_":"hello world","string":"hello world","faststr":"hello world","cow":"borrowed","vector":[42,24,7],"array":[99],"empty_array":[],"map":{"key2":2.2,"key1":1.1},"map_opkey":{"key1":1.1},"option":"I'm here","fieldenum":{"Tuple":["test",42]},"tuple":[42,"test"],"tuple_struct":[42,3.33],"unit_struct":null,"wrapper":"hello","phan_struct":{"phan":"test data","_data":null},"unknown":0,"unknown":null,"unknown":1234e123,"unknown":1.234,"unknown":[],"unknown":{},"unknown":{"a":[]},"unknown":[1,2,3]}"#;

        let expect: TestData = serde_json::from_str(json).unwrap();
        let val: TestData = from_str(json).unwrap();
        assert_eq!(val, expect);
    }

    #[test]
    fn test_serde_time() {
        use chrono::{DateTime, Utc};

        let time: DateTime<Utc> = Utc::now();
        let out = to_string_pretty(&time).unwrap();
        let got = from_str::<DateTime<Utc>>(&out).unwrap();
        assert_eq!(time, got);
    }

    fn read_file(path: &str, vec: &mut Vec<u8>) {
        use std::io::Read;
        let root = env!("CARGO_MANIFEST_DIR").to_owned();
        std::fs::File::open(root + "/benches/testdata/" + path)
            .unwrap()
            .read_to_end(vec)
            .unwrap();
    }

    #[test]
    fn test_struct() {
        use json_benchmark::{citm_catalog::CitmCatalog, twitter::Twitter};
        let mut vec = Vec::new();
        read_file("twitter.json", &mut vec);
        let _value: Twitter = from_slice(&vec).unwrap();

        let mut vec = Vec::new();
        read_file("citm_catalog.json", &mut vec);
        let _value: CitmCatalog = from_slice(&vec).unwrap();
    }

    #[derive(Debug, Deserialize, Serialize, PartialEq)]
    struct TestJsonNumber {
        num: Number,
        raw_num: RawNumber,
    }

    #[test]
    fn test_json_number() {
        let number: RawNumber = from_str("  123").unwrap();
        assert_eq!(number, RawNumber::new("123"));
        assert_eq!(to_string(&number).unwrap(), "123");

        let number: RawNumber = from_str(r#""0.123""#).unwrap();
        assert_eq!(number, RawNumber::new("0.123"));
        assert_eq!(to_string(&number).unwrap(), "0.123");
        assert!(number.is_f64());
        assert_eq!(number.as_f64().unwrap(), 0.123);
        assert_eq!(number.as_u64(), None);

        let num: Number = number.try_into().unwrap();
        assert_eq!(num.as_f64().unwrap(), 0.123);
        assert_eq!(num.as_u64(), None);

        let data = TestJsonNumber {
            num: Number::from_f64(1.23).unwrap(),
            raw_num: RawNumber::new("1.23e123"),
        };
        let expect = r#"{"num":1.23,"raw_num":1.23e123}"#;
        let got = to_string(&data).expect("Failed to serialize the data");
        assert_eq!(expect, got);
        println!("serialized json is {}", got);

        let got_value: TestJsonNumber = from_str(expect).expect("Failed to deserialize the data");
        assert_eq!(data, got_value);
    }

    #[test]
    fn test_json_number_invalid() {
        fn test_json_failed(json: &str) {
            let ret: Result<RawNumber> = from_str(json);
            assert!(ret.is_err(), "invalid json is {}", json);
        }
        test_json_failed(r#"0."#);
        test_json_failed(r#"-"#);
        test_json_failed(r#"-1e"#);
        test_json_failed(r#"-1e-"#);
        test_json_failed(r#"-1e-1.111"#);
        test_json_failed(r#"-1e-1,"#);
        test_json_failed(
            r#""0.123#);
        test_json_failed(r#""-""#,
        );
        test_json_failed(r#""-1e""#);
    }

    #[test]
    fn test_invalid_utf8() {
        let data = [b'"', 0, 0, 0, 0x80, 0x90, b'"'];
        let value: crate::Result<String> = from_slice(&data);
        assert_eq!(
            value.err().unwrap().to_string(),
            "Invalid UTF-8 characters in json at line 1 column 4\n\n\t\"\0\0\0��\"\n\t....^..\n"
        );
    }

    macro_rules! test_struct {
        ($ty:ty, $data:expr) => {
            match serde_json::from_slice::<$ty>($data) {
                Ok(jv) => {
                    let sv = crate::from_slice::<$ty>($data).expect(&format!(
                        "parse valid json {:?} failed for type {}",
                        $data,
                        stringify!($ty)
                    ));
                    assert_eq!(sv, jv);

                    // fuzz the struct to_string
                    let sout = crate::to_string(&sv).unwrap();
                    let jout = serde_json::to_string(&jv).unwrap();
                    let sv = crate::from_str::<$ty>(&sout).unwrap();
                    let jv = serde_json::from_str::<$ty>(&jout).unwrap();
                    assert_eq!(sv, jv);
                }
                Err(err) => {
                    let _ = crate::from_slice::<$ty>($data).expect_err(&format!(
                        "parse invalid json {:?} wrong for type {}, should error: {}",
                        $data,
                        stringify!($ty),
                        err
                    ));
                }
            }
        };
    }

    // the testcase is found by fuzzing tests
    #[test]
    fn test_more_structs() {
        // invalid json: has control chars
        test_struct!(String, &[34, 58, 55, 10, 0, 34, 32, 10]);
        test_struct!(String, &[34, b'\\', b't', 9, 34]);
        test_struct!(String, &[34, 92, 34, 34]);
        test_struct!(String, b"\"\\umap9map009\"");
        test_struct!(Foo, &b"[\"5XXXXXXZX:XXZX:[\",-0]"[..]);
    }
}