From 18aa1a5090bfd882491bf00889e999db89379a5c Mon Sep 17 00:00:00 2001 From: Tom Brzozowski Date: Wed, 7 May 2025 18:56:29 +0100 Subject: [PATCH 1/2] performance improvements --- sje/array_of_objects/custom.rs | 30 +++++++ sje/array_of_objects/decoder.rs | 74 +++++++++++++++ sje/array_of_objects/iter.rs | 117 ++++++++++++++++++++++++ sje/benches/ticker.rs | 2 +- sje/src/macros.rs | 66 +++++++++++--- sje/src/scanner.rs | 127 +++++++++++++++++++++----- sje/tests/array_of_objects.rs | 155 ++++++++++++++++++++++++++++++++ 7 files changed, 537 insertions(+), 34 deletions(-) create mode 100644 sje/array_of_objects/custom.rs create mode 100644 sje/array_of_objects/decoder.rs create mode 100644 sje/array_of_objects/iter.rs create mode 100644 sje/tests/array_of_objects.rs diff --git a/sje/array_of_objects/custom.rs b/sje/array_of_objects/custom.rs new file mode 100644 index 0000000..bb8a8f2 --- /dev/null +++ b/sje/array_of_objects/custom.rs @@ -0,0 +1,30 @@ +use sje_derive::Decoder; +use std::str::FromStr; + +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +struct Price(u64); + +impl FromStr for Price { + type Err = (); + + fn from_str(s: &str) -> Result { + Ok(Self(s.parse().map_err(|_| ())?)) + } +} + +#[derive(Decoder)] +#[sje(object)] +#[allow(dead_code)] +pub struct Trade { + #[sje(rename = "p", ty = "string")] + price: Price, +} + +#[test] +fn should_parse_custom_field() { + let json = r#"{"p":"12345"}"#; + let trade = TradeDecoder::decode(json.as_bytes()).unwrap(); + assert_eq!(&Price(12345), trade.price_as_lazy_field().get_ref().unwrap()); + assert_eq!(Price(12345), trade.price_as_lazy_field().get().unwrap()); + assert_eq!(Price(12345), trade.price()); +} diff --git a/sje/array_of_objects/decoder.rs b/sje/array_of_objects/decoder.rs new file mode 100644 index 0000000..3d5f7b2 --- /dev/null +++ b/sje/array_of_objects/decoder.rs @@ -0,0 +1,74 @@ +use sje_derive::Decoder; + +#[derive(Decoder)] +#[sje(object)] +#[allow(dead_code)] +pub struct Trade { + #[sje(rename = "e", len = 5)] + event_type: String, + #[sje(rename = "E", len = 13)] + event_time: u64, + #[sje(rename = "s")] + symbol: String, + #[sje(rename = "t", len = 10)] + trade_id: u64, + #[sje(rename = "p")] + price: String, + #[sje(rename = "q")] + quantity: String, + #[sje(rename = "b", len = 11)] + buyer_order_id: u64, + #[sje(rename = "a", len = 11)] + seller_order_id: u64, + #[sje(rename = "T", len = 13)] + transaction_time: u64, + #[sje(rename = "m")] + is_buyer_maker: bool, +} + +#[derive(Decoder, Debug)] +#[sje(object)] +#[allow(dead_code)] +struct ListenKeyExpired { + #[sje(rename = "e", len = 16, offset = 1)] + event_type: String, + #[sje(rename = "E", ty = "string", len = 13, offset = 1)] + event_time: u64, + #[sje(rename = "listenKey", offset = 1)] + listen_key: String, +} + +#[cfg(test)] +mod tests { + use crate::{ListenKeyExpiredDecoder, Trade, TradeDecoder}; + use std::str::from_utf8_unchecked; + + #[test] + fn should_decode_trade() { + let trade = TradeDecoder::decode(br#"{"e":"trade","E":1705085312569,"s":"BTCUSDT","t":3370034463,"p":"43520.00000000","q":"0.00022000","b":24269765071,"a":24269767699,"T":1705085312568,"m":true,"M":true}"#).unwrap(); + assert_eq!("trade", trade.event_type()); + assert_eq!("BTCUSDT", unsafe { from_utf8_unchecked(trade.symbol_as_slice()) }); + assert_eq!("BTCUSDT", trade.symbol_as_str()); + assert_eq!("BTCUSDT", trade.symbol()); + + let trade: Trade = trade.into(); + assert_eq!("trade", trade.event_type); + assert_eq!(1705085312569, trade.event_time); + assert_eq!("BTCUSDT", trade.symbol); + assert_eq!(3370034463, trade.trade_id); + assert_eq!("43520.00000000", trade.price); + assert_eq!("0.00022000", trade.quantity); + assert_eq!(24269765071, trade.buyer_order_id); + assert_eq!(24269767699, trade.seller_order_id); + assert_eq!(1705085312568, trade.transaction_time); + assert!(trade.is_buyer_maker); + } + + #[test] + fn should_decode_listen_key_expired() { + let listen_key_expired = ListenKeyExpiredDecoder::decode(br#"{"e": "listenKeyExpired","E": "1743606297156","listenKey": "FdffIUjdfd343DtLMw2tKS87iL2HpYRniDWpkoxWCb4fwP2yzJXalBlBNnz471cE"}"#).unwrap(); + assert_eq!("listenKeyExpired", listen_key_expired.event_type()); + assert_eq!(1743606297156, listen_key_expired.event_time()); + assert_eq!("FdffIUjdfd343DtLMw2tKS87iL2HpYRniDWpkoxWCb4fwP2yzJXalBlBNnz471cE", listen_key_expired.listen_key()); + } +} diff --git a/sje/array_of_objects/iter.rs b/sje/array_of_objects/iter.rs new file mode 100644 index 0000000..603a9ee --- /dev/null +++ b/sje/array_of_objects/iter.rs @@ -0,0 +1,117 @@ +use sje_derive::Decoder; +use std::str::FromStr; + +#[derive(Debug, Copy, Clone, PartialEq)] +#[repr(transparent)] +pub struct Price(f64); + +impl FromStr for Price { + type Err = ::Err; + + fn from_str(s: &str) -> Result { + Ok(Self(s.parse()?)) + } +} + +#[derive(Debug, Copy, Clone, PartialEq)] +#[repr(transparent)] +pub struct Quantity(f64); + +impl FromStr for Quantity { + type Err = ::Err; + + fn from_str(s: &str) -> Result { + Ok(Self(s.parse()?)) + } +} + +#[derive(Decoder, Debug)] +#[sje(object)] +#[allow(dead_code)] +pub struct L2Update { + #[sje(rename = "e", len = 11)] + event_type: String, + #[sje(rename = "b")] + bids: Vec<(Price, Quantity)>, + #[sje(rename = "a")] + asks: Vec<(Price, Quantity)>, +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn should_iterate_over_bids_and_asks() { + let update = L2UpdateDecoder::decode( + br#"{"e":"depthUpdate","b":[["2.6461","6404.9"],["2.6468","22540.8"]],"a":[["2.6461","6404.9"],["2.6468","22540.8"]]}"#, + ).unwrap(); + let mut bids = update.bids().into_iter(); + assert_eq!(2, update.bids_count()); + assert_eq!(2, bids.len()); + assert_eq!(Some((Price(2.6461), Quantity(6404.9))), bids.next()); + assert_eq!(1, bids.len()); + assert_eq!(Some((Price(2.6468), Quantity(22540.8))), bids.next()); + assert_eq!(0, bids.len()); + assert_eq!(None, bids.next()); + assert_eq!(0, bids.len()); + let mut asks = update.asks().into_iter(); + assert_eq!(2, update.asks_count()); + assert_eq!(2, asks.len()); + assert_eq!(Some((Price(2.6461), Quantity(6404.9))), asks.next()); + assert_eq!(1, asks.len()); + assert_eq!(Some((Price(2.6468), Quantity(22540.8))), asks.next()); + assert_eq!(0, asks.len()); + assert_eq!(None, asks.next()); + assert_eq!(0, asks.len()); + + let update = + L2UpdateDecoder::decode(br#"{"e":"depthUpdate","b":[["2.6461","6404.9"]],"a":[["2.6461","6404.9"]]}"#) + .unwrap(); + let mut bids = update.bids().into_iter(); + assert_eq!(1, update.bids_count()); + assert_eq!(1, bids.len()); + assert_eq!(Some((Price(2.6461), Quantity(6404.9))), bids.next()); + assert_eq!(0, bids.len()); + assert_eq!(None, bids.next()); + assert_eq!(0, bids.len()); + let mut asks = update.asks().into_iter(); + assert_eq!(1, update.asks_count()); + assert_eq!(1, asks.len()); + assert_eq!(Some((Price(2.6461), Quantity(6404.9))), asks.next()); + assert_eq!(0, asks.len()); + assert_eq!(None, asks.next()); + assert_eq!(0, asks.len()); + + let update = L2UpdateDecoder::decode(br#"{"e":"depthUpdate","b":[],"a":[]}"#).unwrap(); + let mut bids = update.bids().into_iter(); + assert_eq!(0, update.bids_count()); + assert_eq!(0, bids.len()); + assert_eq!(None, bids.next()); + assert_eq!(0, bids.len()); + let mut asks = update.asks().into_iter(); + assert_eq!(0, update.asks_count()); + assert_eq!(0, asks.len()); + assert_eq!(None, asks.next()); + assert_eq!(0, asks.len()); + } + + #[test] + fn should_convert_to_owned() { + let update = L2UpdateDecoder::decode( + br#"{"e":"depthUpdate","b":[["2.6461","6404.9"],["2.6468","22540.8"]],"a":[["2.6461","6404.9"],["2.6468","22540.8"]]}"#, + ).unwrap(); + let update: L2Update = update.into(); + assert_eq!("depthUpdate", update.event_type); + + let mut bids = update.bids.into_iter(); + assert_eq!(Some((Price(2.6461), Quantity(6404.9))), bids.next()); + assert_eq!(Some((Price(2.6468), Quantity(22540.8))), bids.next()); + assert_eq!(None, bids.next()); + + let mut asks = update.asks.into_iter(); + assert_eq!(Some((Price(2.6461), Quantity(6404.9))), asks.next()); + assert_eq!(Some((Price(2.6468), Quantity(22540.8))), asks.next()); + assert_eq!(None, asks.next()); + } +} diff --git a/sje/benches/ticker.rs b/sje/benches/ticker.rs index 7e2fef4..bacf3a4 100644 --- a/sje/benches/ticker.rs +++ b/sje/benches/ticker.rs @@ -8,7 +8,7 @@ const JSON: &[u8] = br#"{"e":"bookTicker","u":6780157666962,"s":"BTCUSDT","b":"9 #[sje(object)] #[allow(dead_code)] pub struct Ticker { - #[sje(rename = "e", len = 10, offset = 2)] + #[sje(rename = "e", len = 10)] #[serde(rename = "e")] event_type: String, #[sje(rename = "u", len = 13)] diff --git a/sje/src/macros.rs b/sje/src/macros.rs index 8963ac1..3dd46fc 100644 --- a/sje/src/macros.rs +++ b/sje/src/macros.rs @@ -47,25 +47,67 @@ macro_rules! composite_impl { ($method_name:ident, $open_char:literal, $close_char:literal) => { impl<'a> JsonScanner<'a> { #[inline] - pub fn $method_name(&mut self) -> Option<(usize, usize)> { - let offset = self.cursor; - let mut counter = 1u32; - for (index, &item) in unsafe { self.bytes.get_unchecked(offset + 1..) } - .iter() - .enumerate() - { - match item { + pub const fn $method_name(&mut self) -> Option<(usize, usize)> { + let bytes = self.bytes; + let start = self.cursor; + let mut counter: u32 = 1; + let mut i: usize = 0; + + loop { + // if we've run off the end, give up + let idx = start + 1 + i; + if idx >= bytes.len() { + return None; + } + + // fetch the next byte after the opening char + let b = bytes[idx]; + + // bump the nesting counter + match b { $open_char => counter += 1, $close_char => counter -= 1, - _ => {} + _ => {} } + + // if we've closed the top‐level object, return its span if counter == 0 { - self.cursor += index + 2; - return Some((offset, index + 2)); + self.cursor = start + i + 2; + return Some((start, i + 2)); } + + i += 1; } - None } } }; } + + +// #[macro_export] +// macro_rules! composite_impl { +// ($method_name:ident, $open_char:literal, $close_char:literal) => { +// impl<'a> JsonScanner<'a> { +// #[inline] +// pub fn $method_name(&mut self) -> Option<(usize, usize)> { +// let offset = self.cursor; +// let mut counter = 1u32; +// for (index, &item) in unsafe { self.bytes.get_unchecked(offset + 1..) } +// .iter() +// .enumerate() +// { +// match item { +// $open_char => counter += 1, +// $close_char => counter -= 1, +// _ => {} +// } +// if counter == 0 { +// self.cursor += index + 2; +// return Some((offset, index + 2)); +// } +// } +// None +// } +// } +// }; +// } diff --git a/sje/src/scanner.rs b/sje/src/scanner.rs index ab4e355..5479e97 100644 --- a/sje/src/scanner.rs +++ b/sje/src/scanner.rs @@ -34,34 +34,100 @@ composite_impl!(next_tuple, b'[', b']'); composite_impl!(next_object, b'{', b'}'); impl JsonScanner<'_> { - #[inline] - pub fn next_array(&mut self) -> Option<(usize, usize, usize)> { - let offset = self.cursor; - let mut counter = 1u32; - let mut array_len = 0; - for (index, &item) in unsafe { self.bytes.get_unchecked(offset + 1..) }.iter().enumerate() { - match item { - b'[' => counter += 1, - b']' => counter -= 1, - _ => {} + pub const fn next_array(&mut self) -> Option<(usize, usize, usize)> { + let bytes = self.bytes; + let start = self.cursor; + + // state + let mut array_depth = 0usize; + let mut obj_depth = 0usize; + let mut in_string = false; + let mut escaped = false; + let mut commas = 0usize; + let mut saw_value = false; + let mut index = 0usize; + + // iterate until we run off the end + loop { + // bounds-check + if start + index >= bytes.len() { + return None; } - - if item == b',' && counter == 1 { - array_len += 1; + let b = bytes[start + index]; + + // 1) handle strings & escapes + if in_string { + if escaped { + escaped = false; + } else if b == b'\\' { + escaped = true; + } else if b == b'"' { + in_string = false; + } + index += 1; + continue; + } else if b == b'"' { + in_string = true; + index += 1; + continue; } - if counter == 0 { - if index > 0 { - let previous = unsafe { *self.bytes.get_unchecked(index - 1) } as char; - if previous != '[' { - array_len += 1; + // 2) track nesting and detect top-level elements + match b { + // entering any array + b'[' => { + array_depth += 1; + if array_depth == 1 { + // this is the '[' of *our* array + saw_value = false; + } else if array_depth == 2 { + // nested array => counts as an element + saw_value = true; } } - self.cursor += index + 2; - return Some((offset, index + 2, array_len)); + + // leaving an array + b']' => { + array_depth -= 1; + if array_depth == 0 { + // done with this array + let element_count = if saw_value { commas + 1 } else { 0 }; + // advance cursor past the closing ] + self.cursor = start + index + 1; + return Some((start, index + 1, element_count)); + } + } + + // entering an object (only matters inside our array) + b'{' if array_depth > 0 => { + if array_depth == 1 && obj_depth == 0 { + // top-level object => counts as element + saw_value = true; + } + obj_depth += 1; + } + + // leaving an object + b'}' if array_depth > 0 => { + obj_depth -= 1; + } + + // a comma that really separates two top-level elements + b',' if array_depth == 1 && obj_depth == 0 => { + commas += 1; + saw_value = false; // now look for next element + } + + // any other non-whitespace at top-level marks “we saw a value” + _ if array_depth == 1 && !b.is_ascii_whitespace() => { + saw_value = true; + } + + _ => {} } + + index += 1; } - None } } @@ -187,6 +253,15 @@ mod tests { assert_eq!(1, count); } + #[test] + fn should_scan_bool_array() { + let bytes = br#"[true,false,false]"#; + let mut scanner = JsonScanner::wrap(bytes); + + let (_, _, count) = scanner.next_array().unwrap(); + assert_eq!(3, count) + } + #[test] fn should_scan_object() { let bytes = br#"{"b":{"id":1},"a":[4,5],"E":1704907109810,"c":{"id":1,"foo":{"id":2}}}"#; @@ -256,6 +331,16 @@ mod tests { assert_eq!("-541.56".as_bytes(), &bytes[offset..offset + len]); } + #[test] + fn should_scan_array_of_objects() { + let bytes = br#"[{"s":"btcusdt","a":100},{"s":"ethusdt","a":200}]"#; + let mut scanner = JsonScanner::wrap(bytes); + scanner.skip(0); + let (offset, len, count) = scanner.next_array().unwrap(); + + assert_eq!(2, count) + } + mod decoder { use std::str::from_utf8; diff --git a/sje/tests/array_of_objects.rs b/sje/tests/array_of_objects.rs new file mode 100644 index 0000000..de28b37 --- /dev/null +++ b/sje/tests/array_of_objects.rs @@ -0,0 +1,155 @@ +use sje_derive::Decoder; + +#[derive(Decoder)] +#[sje(object)] +#[allow(dead_code)] +struct Position { + #[sje(rename = "s")] + symbol: String, + #[sje(rename = "a")] + amount: u32, +} + +// #[derive(Decoder)] +// #[sje(object)] +// #[allow(dead_code)] +// struct PositionUpdate { +// #[sje(rename = "t")] +// timestamp: u64, +// #[sje(rename = "u")] +// updates: Vec, +// } + +// implies we have *Decoder, has to be explicit, i.e. decoder = true, otherwise it will try to use from_str + +mod wtf { + use crate::PositionDecoder; + use std::slice::from_raw_parts; + + #[derive(Debug)] + pub struct PositionUpdateDecoder<'a> { + timestamp: sje::LazyField<'a, u64>, + updates: (&'a [u8], usize), + } + impl<'a> PositionUpdateDecoder<'a> { + #[inline] + pub fn decode(bytes: &'a [u8]) -> Result { + let mut scanner = sje::scanner::JsonScanner::wrap(bytes); + scanner.skip(5usize); + let (offset, len) = scanner + .next_number() + .ok_or_else(|| sje::error::Error::MissingField("timestamp"))?; + let timestamp = sje::LazyField::from_bytes(unsafe { bytes.get_unchecked(offset..offset + len) }); + scanner.skip(5usize); + let (offset, len, count) = scanner + .next_array() + .ok_or_else(|| sje::error::Error::MissingField("updates"))?; + let updates = (unsafe { bytes.get_unchecked(offset..offset + len) }, count); + Ok(Self { timestamp, updates }) + } + } + impl<'a> PositionUpdateDecoder<'a> { + #[inline] + pub const fn timestamp_as_slice(&self) -> &[u8] { + self.timestamp.as_slice() + } + #[inline] + pub const fn timestamp_as_str(&self) -> &str { + self.timestamp.as_str() + } + #[inline] + pub const fn timestamp_as_lazy_field(&self) -> &sje::LazyField<'a, u64> { + &self.timestamp + } + #[inline] + pub const fn updates_as_slice(&self) -> &[u8] { + self.updates.0 + } + #[inline] + pub const fn updates_as_str(&self) -> &str { + unsafe { std::str::from_utf8_unchecked(self.updates_as_slice()) } + } + #[inline] + pub const fn updates_count(&self) -> usize { + self.updates.1 + } + } + impl PositionUpdateDecoder<'_> { + #[inline] + pub fn timestamp(&self) -> u64 { + self.timestamp.get().unwrap() + } + } + #[derive(Debug)] + pub struct Updates<'a> { + bytes: &'a [u8], + remaining: usize, + } + impl PositionUpdateDecoder<'_> { + #[inline] + pub const fn updates(&self) -> Updates { + Updates { + bytes: self.updates.0, + remaining: self.updates.1, + } + } + } + + impl<'a> IntoIterator for Updates<'a> { + type Item = PositionDecoder<'a>; + type IntoIter = UpdatesIter<'a>; + fn into_iter(self) -> Self::IntoIter { + UpdatesIter { + scanner: sje::scanner::JsonScanner::wrap(self.bytes), + remaining: self.remaining, + } + } + } + pub struct UpdatesIter<'a> { + scanner: sje::scanner::JsonScanner<'a>, + remaining: usize, + } + impl<'a> Iterator for UpdatesIter<'a> { + type Item = PositionDecoder<'a>; + #[inline] + fn next(&mut self) -> Option { + if self.scanner.position() + 1 == self.scanner.bytes().len() { return None; } + self.scanner.skip(1); + let (offset, len) = self.scanner.next_object().unwrap(); + self.remaining -= 1; + let bytes = &self.scanner.bytes()[offset..offset + len]; + let bytes = unsafe { from_raw_parts(bytes.as_ptr(), bytes.len()) }; + Some(PositionDecoder::decode(bytes).unwrap()) + } + #[inline] + fn size_hint(&self) -> (usize, Option) { + (self.remaining, Some(self.remaining)) + } + } + impl ExactSizeIterator for UpdatesIter<'_> { + #[inline] + fn len(&self) -> usize { + self.remaining + } + } + + #[test] + fn test() { + let json = r#"{"t":12345,"u":[{"s":"btcusdt","a":100},{"s":"ethusdt","a":200}]}"#; + + let update = PositionUpdateDecoder::decode(json.as_bytes()).unwrap(); + assert_eq!(2, update.updates_count()); + + let mut positions = update.updates().into_iter(); + + let position = positions.next().unwrap(); + assert_eq!("btcusdt", position.symbol_as_str()); + assert_eq!(100, position.amount()); + + let position = positions.next().unwrap(); + assert_eq!("ethusdt", position.symbol_as_str()); + assert_eq!(200, position.amount()); + + assert!(positions.next().is_none()); + } +} From 0a21f93aeca6574a9b0c9beab5de37fc1f0c36f3 Mon Sep 17 00:00:00 2001 From: Tom Brzozowski Date: Thu, 8 May 2025 11:44:17 +0100 Subject: [PATCH 2/2] support array of objects decoder --- README.md | 83 +++++++++++- docs/benchmark.png | Bin 0 -> 49489 bytes sje/src/macros.rs | 13 +- sje/src/scanner.rs | 3 +- sje/tests/array_of_objects.rs | 158 +++------------------- sje_derive/src/lib.rs | 244 ++++++++++++++++++++++++---------- 6 files changed, 283 insertions(+), 218 deletions(-) create mode 100644 docs/benchmark.png diff --git a/README.md b/README.md index a11aea2..6ebdd0f 100644 --- a/README.md +++ b/README.md @@ -3,11 +3,11 @@ [![Documentation](https://docs.rs/sje/badge.svg)](https://docs.rs/sje/) [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE) -# sje +# Simple Json Encoding Fast JSON deserialisation and serialisation schema based framework. -## Example +## Examples Make sure the `derive` feature is enabled. @@ -15,6 +15,9 @@ Make sure the `derive` feature is enabled. sje = { version = "0.0.4", features = ["derive"]} ``` +Simply annotate your struct using `#[sje]` attribute, The `len` field can be used when you know the exact size of the value field which means the +decoder can handle it more efficiently. + ```rust #[derive(Decoder)] #[sje(object)] @@ -46,4 +49,80 @@ assert_eq!("trade", trade.event_type_as_str()); assert_eq!(1705085312569, trade.event_time()); ``` +We can also handle arrays and tuples. In this case if we want to use generated `PositionDecoder` we need to explicitly mark it with `decoder = true`. + +```rust +#[derive(Decoder)] +#[sje(object)] +struct Position { + #[sje(rename = "s")] + symbol: String, + #[sje(rename = "a")] + amount: u32, +} + +#[derive(Decoder)] +#[sje(object)] +struct PositionUpdate { + #[sje(rename = "t")] + timestamp: u64, + #[sje(rename = "u", decoder = true)] + updates: Vec, +} +``` + +The generated code will contain iterators that already know the length of the array. + +```rust +let update = PositionUpdateDecoder::decode(br#"{"t":1746699621,"u":[{"s":"btcusdt","a":100},{"s":"ethusdt","a":200}]}"#).unwrap(); +assert_eq!(2, update.updates_count()); + +let mut updates = update.updates().into_iter(); + +let position = updates.next().unwrap(); +assert_eq!("btcusdt", position.symbol_as_str()); +assert_eq!(100, position.amount()); + +let position = updates.next().unwrap(); +assert_eq!("ethusdt", position.symbol_as_str()); +assert_eq!(200, position.amount()); +assert!(positions.next().is_none()); +``` + +The framework also handles user defined types that don't require an explicit `Decoder`. In this case, the only requirement is that the type +implements `FromStr` trait. We also need to tell the parser what is the underlying json type for our user defined type, in this case `ty = "string"`. + +```rust +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +struct Price(u64); + +impl FromStr for Price { + type Err = &'static str; + + fn from_str(s: &str) -> Result { + Ok(Self(s.parse().map_err(|_| "unable to parse the price")?)) + } +} + +#[derive(Decoder)] +#[sje(object)] +pub struct Trade { + #[sje(rename = "p", ty = "string")] + price: Price, +} + +let trade = TradeDecoder::decode(br#"{"p":"12345"}"#).unwrap(); +assert_eq!(Price(12345), trade.price()); +``` + +## Benchmarks + +There are [benchmarks](sje/benches) against [serde_json](https://crates.io/crates/serde_json) that show an order of magnitude +speedup. Please note `sje` is not a generic purpose json parser - it's fast because it takes advantage of fixed schema and +lacks a lot of features you would find in `serde_json` to handle more dynamic content. + +```shell +RUSTFLAGS='-C target-cpu=native' cargo bench --bench=ticker +``` +![img.png](docs/benchmark.png) \ No newline at end of file diff --git a/docs/benchmark.png b/docs/benchmark.png new file mode 100644 index 0000000000000000000000000000000000000000..647faad77334a6973ee23e4062d6c7fbfd4ad475 GIT binary patch literal 49489 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 bytes.len() { return None; } - + // fetch the next byte after the opening char let b = bytes[idx]; - + // bump the nesting counter match b { $open_char => counter += 1, $close_char => counter -= 1, - _ => {} + _ => {} } - + // if we've closed the top‐level object, return its span if counter == 0 { self.cursor = start + i + 2; return Some((start, i + 2)); } - + i += 1; } } @@ -83,7 +83,6 @@ macro_rules! composite_impl { }; } - // #[macro_export] // macro_rules! composite_impl { // ($method_name:ident, $open_char:literal, $close_char:literal) => { diff --git a/sje/src/scanner.rs b/sje/src/scanner.rs index 5479e97..c7dacae 100644 --- a/sje/src/scanner.rs +++ b/sje/src/scanner.rs @@ -336,8 +336,7 @@ mod tests { let bytes = br#"[{"s":"btcusdt","a":100},{"s":"ethusdt","a":200}]"#; let mut scanner = JsonScanner::wrap(bytes); scanner.skip(0); - let (offset, len, count) = scanner.next_array().unwrap(); - + let (_, _, count) = scanner.next_array().unwrap(); assert_eq!(2, count) } diff --git a/sje/tests/array_of_objects.rs b/sje/tests/array_of_objects.rs index de28b37..5b15bf1 100644 --- a/sje/tests/array_of_objects.rs +++ b/sje/tests/array_of_objects.rs @@ -10,146 +10,32 @@ struct Position { amount: u32, } -// #[derive(Decoder)] -// #[sje(object)] -// #[allow(dead_code)] -// struct PositionUpdate { -// #[sje(rename = "t")] -// timestamp: u64, -// #[sje(rename = "u")] -// updates: Vec, -// } - -// implies we have *Decoder, has to be explicit, i.e. decoder = true, otherwise it will try to use from_str - -mod wtf { - use crate::PositionDecoder; - use std::slice::from_raw_parts; +#[derive(Decoder)] +#[sje(object)] +#[allow(dead_code)] +struct PositionUpdate { + #[sje(rename = "t")] + timestamp: u64, + #[sje(rename = "u", decoder = true)] + updates: Vec, +} - #[derive(Debug)] - pub struct PositionUpdateDecoder<'a> { - timestamp: sje::LazyField<'a, u64>, - updates: (&'a [u8], usize), - } - impl<'a> PositionUpdateDecoder<'a> { - #[inline] - pub fn decode(bytes: &'a [u8]) -> Result { - let mut scanner = sje::scanner::JsonScanner::wrap(bytes); - scanner.skip(5usize); - let (offset, len) = scanner - .next_number() - .ok_or_else(|| sje::error::Error::MissingField("timestamp"))?; - let timestamp = sje::LazyField::from_bytes(unsafe { bytes.get_unchecked(offset..offset + len) }); - scanner.skip(5usize); - let (offset, len, count) = scanner - .next_array() - .ok_or_else(|| sje::error::Error::MissingField("updates"))?; - let updates = (unsafe { bytes.get_unchecked(offset..offset + len) }, count); - Ok(Self { timestamp, updates }) - } - } - impl<'a> PositionUpdateDecoder<'a> { - #[inline] - pub const fn timestamp_as_slice(&self) -> &[u8] { - self.timestamp.as_slice() - } - #[inline] - pub const fn timestamp_as_str(&self) -> &str { - self.timestamp.as_str() - } - #[inline] - pub const fn timestamp_as_lazy_field(&self) -> &sje::LazyField<'a, u64> { - &self.timestamp - } - #[inline] - pub const fn updates_as_slice(&self) -> &[u8] { - self.updates.0 - } - #[inline] - pub const fn updates_as_str(&self) -> &str { - unsafe { std::str::from_utf8_unchecked(self.updates_as_slice()) } - } - #[inline] - pub const fn updates_count(&self) -> usize { - self.updates.1 - } - } - impl PositionUpdateDecoder<'_> { - #[inline] - pub fn timestamp(&self) -> u64 { - self.timestamp.get().unwrap() - } - } - #[derive(Debug)] - pub struct Updates<'a> { - bytes: &'a [u8], - remaining: usize, - } - impl PositionUpdateDecoder<'_> { - #[inline] - pub const fn updates(&self) -> Updates { - Updates { - bytes: self.updates.0, - remaining: self.updates.1, - } - } - } +#[test] +fn should_decode_array_of_objects() { + let json = r#"{"t":1746699621,"u":[{"s":"btcusdt","a":100},{"s":"ethusdt","a":200}]}"#; - impl<'a> IntoIterator for Updates<'a> { - type Item = PositionDecoder<'a>; - type IntoIter = UpdatesIter<'a>; - fn into_iter(self) -> Self::IntoIter { - UpdatesIter { - scanner: sje::scanner::JsonScanner::wrap(self.bytes), - remaining: self.remaining, - } - } - } - pub struct UpdatesIter<'a> { - scanner: sje::scanner::JsonScanner<'a>, - remaining: usize, - } - impl<'a> Iterator for UpdatesIter<'a> { - type Item = PositionDecoder<'a>; - #[inline] - fn next(&mut self) -> Option { - if self.scanner.position() + 1 == self.scanner.bytes().len() { return None; } - self.scanner.skip(1); - let (offset, len) = self.scanner.next_object().unwrap(); - self.remaining -= 1; - let bytes = &self.scanner.bytes()[offset..offset + len]; - let bytes = unsafe { from_raw_parts(bytes.as_ptr(), bytes.len()) }; - Some(PositionDecoder::decode(bytes).unwrap()) - } - #[inline] - fn size_hint(&self) -> (usize, Option) { - (self.remaining, Some(self.remaining)) - } - } - impl ExactSizeIterator for UpdatesIter<'_> { - #[inline] - fn len(&self) -> usize { - self.remaining - } - } + let update = PositionUpdateDecoder::decode(json.as_bytes()).unwrap(); + assert_eq!(2, update.updates_count()); - #[test] - fn test() { - let json = r#"{"t":12345,"u":[{"s":"btcusdt","a":100},{"s":"ethusdt","a":200}]}"#; + let mut positions = update.updates().into_iter(); - let update = PositionUpdateDecoder::decode(json.as_bytes()).unwrap(); - assert_eq!(2, update.updates_count()); - - let mut positions = update.updates().into_iter(); + let position = positions.next().unwrap(); + assert_eq!("btcusdt", position.symbol_as_str()); + assert_eq!(100, position.amount()); - let position = positions.next().unwrap(); - assert_eq!("btcusdt", position.symbol_as_str()); - assert_eq!(100, position.amount()); + let position = positions.next().unwrap(); + assert_eq!("ethusdt", position.symbol_as_str()); + assert_eq!(200, position.amount()); - let position = positions.next().unwrap(); - assert_eq!("ethusdt", position.symbol_as_str()); - assert_eq!(200, position.amount()); - - assert!(positions.next().is_none()); - } + assert!(positions.next().is_none()); } diff --git a/sje_derive/src/lib.rs b/sje_derive/src/lib.rs index 2dc261d..e632013 100644 --- a/sje_derive/src/lib.rs +++ b/sje_derive/src/lib.rs @@ -6,8 +6,8 @@ use std::str::FromStr; use syn::parse::{Parse, ParseStream}; use syn::spanned::Spanned; use syn::{ - parse_macro_input, Data, DataEnum, DataStruct, DeriveInput, Error, Fields, Ident, LitInt, LitStr, PathArguments, - PathSegment, Token, Type, + parse_macro_input, Data, DataEnum, DataStruct, DeriveInput, Error, Fields, Ident, LitBool, LitInt, LitStr, + PathArguments, PathSegment, Token, Type, TypePath, }; #[derive(Debug, Copy, Clone)] @@ -58,6 +58,7 @@ struct SjeFieldAttribute { also_as: Option, /// offset at which value begins offset: usize, + decoder: bool, } impl Parse for SjeFieldAttribute { @@ -67,6 +68,7 @@ impl Parse for SjeFieldAttribute { let mut ty = None; let mut also_as = None; let mut offset = 0; + let mut decoder = false; while !input.is_empty() { let lookahead = input.lookahead1(); @@ -92,6 +94,10 @@ impl Parse for SjeFieldAttribute { input.parse::()?; let offset_lit: LitInt = input.parse()?; offset = offset_lit.base10_parse()?; + } else if ident == "decoder" { + input.parse::()?; + let decoder_lit: LitBool = input.parse()?; + decoder = decoder_lit.value(); } else { return Err(syn::Error::new_spanned(ident, "expected ['len' | 'rename' | 'ty']")); } @@ -111,6 +117,7 @@ impl Parse for SjeFieldAttribute { ty, also_as, offset, + decoder, }) } } @@ -346,6 +353,12 @@ fn handle_sje_object(name: &syn::Ident, data_struct: DataStruct, _sje_attr: SjeA }); let iterators = fields.iter().map(|field| { + let mut decoder = false; + if let Some(sje_attr) = field.attrs.iter().find(|attr| attr.path().is_ident("sje")) { + let sje_field = sje_attr.parse_args::().expect("unable to parse"); + decoder = sje_field.decoder + } + let field_name = &field.ident; let field_type = &field.ty; @@ -359,8 +372,9 @@ fn handle_sje_object(name: &syn::Ident, data_struct: DataStruct, _sje_attr: SjeA let array_fn_name = format_ident!("{}", field_name.as_ref().unwrap().to_string()); let iterator_name = format_ident!("{}Iter", field_name.as_ref().unwrap().to_string().to_upper_camel_case()); - let next_impl = iterator_next_impl(arg_type); - return quote! { + let next_impl = iterator_next_impl(arg_type, decoder); + + let mut code = quote! { #[derive(Debug)] pub struct #array_struct_name<'a> { bytes: &'a [u8], @@ -373,52 +387,90 @@ fn handle_sje_object(name: &syn::Ident, data_struct: DataStruct, _sje_attr: SjeA #array_struct_name { bytes: self.#array_fn_name.0, remaining: self.#array_fn_name.1 } } } + pub struct #iterator_name<'a> { + scanner: sje::scanner::JsonScanner<'a>, + remaining: usize, + } + impl ExactSizeIterator for #iterator_name<'_> { - impl From<#array_struct_name<'_>> for Vec<#arg_type> { - fn from(value: #array_struct_name) -> Self { - value.into_iter().collect() + #[inline] + fn len(&self) -> usize { + self.remaining } } + }; - impl<'a> IntoIterator for #array_struct_name<'a> { - type Item = #arg_type; - type IntoIter = #iterator_name<'a>; - - fn into_iter(self) -> Self::IntoIter { - #iterator_name { - scanner: sje::scanner::JsonScanner::wrap(self.bytes), - remaining: self.remaining + if decoder { + let arg_type_decoder = format_ident!("{}Decoder", type_to_ident(arg_type).unwrap()); + code.extend(quote! { + impl <'a> From<#array_struct_name<'a>> for Vec<#arg_type_decoder<'a>> { + fn from(value: #array_struct_name<'a>) -> Self { + value.into_iter().collect() } } - } - pub struct #iterator_name<'a> { - scanner: sje::scanner::JsonScanner<'a>, - remaining: usize, - } + impl<'a> IntoIterator for #array_struct_name<'a> { + type Item = #arg_type_decoder<'a>; + type IntoIter = #iterator_name<'a>; + fn into_iter(self) -> Self::IntoIter { + #iterator_name { + scanner: sje::scanner::JsonScanner::wrap(self.bytes), + remaining: self.remaining + } + } + } + impl <'a> Iterator for #iterator_name<'a> { + type Item = #arg_type_decoder<'a>; + #[inline] + fn next(&mut self) -> Option { + #next_impl + } + #[inline] + fn size_hint(&self) -> (usize, Option) { + (self.remaining, Some(self.remaining)) + } + } + impl From<#array_struct_name<'_>> for Vec<#arg_type> { + fn from(value: #array_struct_name<'_>) -> Self { + value.into_iter().map(|decoder| decoder.into()).collect() + } + } + }); + } else { + code.extend(quote! { + impl From<#array_struct_name<'_>> for Vec<#arg_type> { + fn from(value: #array_struct_name) -> Self { + value.into_iter().collect() + } + } - impl Iterator for #iterator_name<'_> { - type Item = #arg_type; + impl<'a> IntoIterator for #array_struct_name<'a> { + type Item = #arg_type; + type IntoIter = #iterator_name<'a>; - #[inline] - fn next(&mut self) -> Option { - #next_impl + fn into_iter(self) -> Self::IntoIter { + #iterator_name { + scanner: sje::scanner::JsonScanner::wrap(self.bytes), + remaining: self.remaining + } + } } - #[inline] - fn size_hint(&self) -> (usize, Option) { - (self.remaining, Some(self.remaining)) - } - } + impl Iterator for #iterator_name<'_> { + type Item = #arg_type; - impl ExactSizeIterator for #iterator_name<'_> { - - #[inline] - fn len(&self) -> usize { - self.remaining + #[inline] + fn next(&mut self) -> Option { + #next_impl + } + #[inline] + fn size_hint(&self) -> (usize, Option) { + (self.remaining, Some(self.remaining)) + } } - } - }; + }); + } + return code; } } } @@ -480,48 +532,87 @@ fn resolve_type(ty: &Type, ty_override: Option) -> syn::Result<&'static } } -fn iterator_next_impl(ty: &Type) -> proc_macro2::TokenStream { - if let Type::Tuple(tuple) = ty { - // Generate code for processing each element - let mut code = quote! {}; - let mut tuple_values = Vec::new(); +fn iterator_next_impl(ty: &Type, decoder: bool) -> proc_macro2::TokenStream { + match ty { + Type::Path(path) => { + let mut code = quote! {}; + let mut p = path.path.clone(); + if let Some(last) = p.segments.last_mut() { + // last.ident = format_ident!("{}Decoder", last.ident); + let ident = match decoder { + true => format_ident!("{}Decoder", last.ident.clone()), + false => format_ident!("{}", last.ident.clone()), + }; - code.extend(quote! { - if self.scanner.position() + 1 == self.scanner.bytes().len() { - return None; - } - self.scanner.skip(1); - let (offset, len) = self.scanner.next_tuple()?; - let mut tuple_scanner = unsafe { sje::scanner::JsonScanner::wrap(self.scanner.bytes().get_unchecked(offset..offset + len)) }; - }); + let last = match decoder { + true => quote! { + Some(#ident::decode(bytes).unwrap()) + }, + false => quote! { + let s = unsafe { std::str::from_utf8_unchecked(bytes) }; + Some(#ident::from_str(s).unwrap()) + }, + }; - // Iterate over the tuple elements and generate code for each element - for (i, _) in tuple.elems.iter().enumerate() { - // Dynamically generate a variable name based on the index - let var_name = format_ident!("val_{i}"); + code.extend(quote! { + if self.scanner.position() + 1 == self.scanner.bytes().len() { + return None; + } + self.scanner.skip(1); + let (offset, len) = self.scanner.next_object()?; + self.remaining -= 1; + + let bytes = &self.scanner.bytes()[offset..offset + len]; + let bytes = unsafe { std::slice::from_raw_parts(bytes.as_ptr(), bytes.len()) }; + #last + // Some(#ident::decode(bytes).unwrap()) + }); + } + code + } + Type::Tuple(tuple) => { + // Generate code for processing each element + let mut code = quote! {}; + let mut tuple_values = Vec::new(); - // Generate the code for processing this element code.extend(quote! { - tuple_scanner.skip(1); - let (offset, len) = tuple_scanner.next_string()?; - let str = unsafe { std::str::from_utf8_unchecked(tuple_scanner.bytes().get_unchecked(offset..offset + len)) }; - let #var_name = str.parse().unwrap(); + if self.scanner.position() + 1 == self.scanner.bytes().len() { + return None; + } + self.scanner.skip(1); + let (offset, len) = self.scanner.next_tuple()?; + let mut tuple_scanner = unsafe { sje::scanner::JsonScanner::wrap(self.scanner.bytes().get_unchecked(offset..offset + len)) }; }); - // Add the variable to the tuple values vector for dynamic construction - tuple_values.push(quote! { #var_name }); - } + // Iterate over the tuple elements and generate code for each element + for (i, _) in tuple.elems.iter().enumerate() { + // Dynamically generate a variable name based on the index + let var_name = format_ident!("val_{i}"); + + // Generate the code for processing this element + code.extend(quote! { + tuple_scanner.skip(1); + let (offset, len) = tuple_scanner.next_string()?; + let str = unsafe { std::str::from_utf8_unchecked(tuple_scanner.bytes().get_unchecked(offset..offset + len)) }; + let #var_name = str.parse().unwrap(); + }); - // Combine the generated code and the `Some(...)` expression - code.extend(quote! { - self.remaining -= 1; - Some((#(#tuple_values),*)) - }); + // Add the variable to the tuple values vector for dynamic construction + tuple_values.push(quote! { #var_name }); + } - code - } else { - // If it's not a tuple, return an empty TokenStream - quote! {} + // Combine the generated code and the `Some(...)` expression + code.extend(quote! { + self.remaining -= 1; + Some((#(#tuple_values),*)) + }); + + code + } + _ => { + // If it's not a tuple, return an empty TokenStream + quote! {} + } } } @@ -538,6 +629,17 @@ fn is_integer_type(ty: &Type) -> bool { false } +/// Try to extract the bare `Ident` from a `&Type::Path`. +fn type_to_ident(ty: &Type) -> Option { + if let Type::Path(TypePath { qself: None, path }) = ty { + // if it's something like `Foo` or `my::crate::Bar`, + // `.segments.last()` is the `Bar` segment + path.segments.last().map(|seg| seg.ident.clone()) + } else { + None + } +} + #[cfg(test)] mod tests { use syn::{parse_quote, parse_str, Attribute};