use serde::de::DeserializeOwned;
use thiserror::Error;
#[derive(Debug, Error)]
pub enum JsonError {
#[error("JSON parse error: {0}")]
Parse(String),
#[error("simd-json not available on this platform")]
Unsupported,
}
impl From<serde_json::Error> for JsonError {
fn from(e: serde_json::Error) -> Self {
JsonError::Parse(e.to_string())
}
}
pub fn from_str<T: DeserializeOwned>(s: &str) -> Result<T, JsonError> {
#[cfg(all(target_arch = "x86_64", feature = "simd-json"))]
{
simd_json_from_str(s)
}
#[cfg(not(all(target_arch = "x86_64", feature = "simd-json")))]
{
serde_json::from_str(s).map_err(JsonError::from)
}
}
#[cfg(all(target_arch = "x86_64", feature = "simd-json"))]
fn simd_json_from_str<T: DeserializeOwned>(s: &str) -> Result<T, JsonError> {
let mut bytes = s.as_bytes().to_vec();
simd_json::from_slice(&mut bytes).map_err(|e| JsonError::Parse(e.to_string()))
}
pub fn from_slice<T: DeserializeOwned>(s: &[u8]) -> Result<T, JsonError> {
#[cfg(all(target_arch = "x86_64", feature = "simd-json"))]
{
let mut bytes = s.to_vec();
simd_json::from_slice(&mut bytes).map_err(|e| JsonError::Parse(e.to_string()))
}
#[cfg(not(all(target_arch = "x86_64", feature = "simd-json")))]
{
serde_json::from_slice(s).map_err(JsonError::from)
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde::Deserialize;
#[derive(Debug, Deserialize, PartialEq)]
struct MediumDto {
code: i64,
msg: String,
data: MediumData,
}
#[derive(Debug, Deserialize, PartialEq)]
struct MediumData {
id: i64,
name: String,
items: Vec<i64>,
}
#[test]
fn test_simd_safe_deserialize_medium() {
let json = r#"{"code":200,"msg":"ok","data":{"id":1,"name":"test","items":[1,2,3]}}"#;
let result: MediumDto = from_str(json).unwrap();
assert_eq!(result.code, 200);
assert_eq!(result.msg, "ok");
assert_eq!(result.data.id, 1);
assert_eq!(result.data.name, "test");
assert_eq!(result.data.items, vec![1, 2, 3]);
}
#[test]
fn test_simd_safe_deserialize_small() {
let json = r#"{"code":200,"msg":"ok"}"#;
#[derive(Debug, Deserialize, PartialEq)]
struct SmallDto {
code: i64,
msg: String,
}
let result: SmallDto = from_str(json).unwrap();
assert_eq!(result.code, 200);
assert_eq!(result.msg, "ok");
}
#[test]
fn test_simd_safe_skip_serializing() {
let json = r#"{"code":200,"msg":"ok"}"#;
#[derive(Debug, serde::Deserialize, serde::Serialize, PartialEq)]
struct DtoWithSensitive {
code: i64,
msg: String,
#[serde(skip_serializing)]
secret: Option<String>,
}
let result: DtoWithSensitive = from_str(json).unwrap();
assert_eq!(result.code, 200);
assert_eq!(result.msg, "ok");
assert_eq!(result.secret, None);
let serialized = serde_json::to_string(&result).unwrap();
assert!(!serialized.contains("secret"));
}
#[test]
fn test_simd_safe_error_mapping() {
let result: Result<i64, _> = from_str("invalid json");
assert!(result.is_err());
match result.unwrap_err() {
JsonError::Parse(_) => {}
JsonError::Unsupported => panic!("should be Parse error"),
}
}
#[test]
fn test_simd_safe_from_slice() {
let json = br#"{"code":200,"msg":"ok"}"#;
#[derive(Debug, Deserialize, PartialEq)]
struct SmallDto {
code: i64,
msg: String,
}
let result: SmallDto = from_slice(json).unwrap();
assert_eq!(result.code, 200);
assert_eq!(result.msg, "ok");
}
}