1 unstable release
new 0.1.0 | Apr 30, 2025 |
---|
#602 in Encoding
50KB
770 lines
serde-evaluate
Extract single scalar field values from Serializeable structs without full deserialization.
lib.rs
:
This library provides a mechanism to extract the value of a single field
from any struct that implements serde::Serialize
without needing to
deserialize the entire struct. It's particularly useful when you only need
one specific piece of data from a potentially large or complex structure,
potentially residing within nested structs or maps.
The extracted value is returned as a FieldScalarValue
enum, which covers
common scalar types (integers, floats, bool, string, char, bytes, unit, and options of these).
How it Works
It uses a custom Serde Serializer
(FieldValueExtractorSerializer
) that intercepts
the serialization process. When the target field path (which can include struct fields
and map keys separated by dots) is encountered, its scalar value is captured.
Serialization of other fields or parts of the structure is skipped efficiently.
Usage
use serde::Serialize;
use std::collections::HashMap;
use serde_evaluate::{extractor::{FieldExtractor, NestedFieldExtractor}, value::FieldScalarValue, error::EvaluateError};
#[derive(Serialize)]
struct MyData {
id: u32,
name: String,
active: bool,
score: Option<f64>,
#[serde(with = "serde_bytes")]
raw_data: Vec<u8>,
nested: InnerData,
data_map: HashMap<String, InnerData>,
}
#[derive(Serialize)]
struct InnerData {
value: i32,
description: Option<String>,
}
fn main() -> Result<(), EvaluateError> {
let mut map = HashMap::new();
map.insert("entry1".to_string(), InnerData { value: -10, description: None });
map.insert("entry2".to_string(), InnerData { value: 20, description: Some("Second".to_string()) });
let data = MyData {
id: 101,
name: "Example".to_string(),
active: true,
score: Some(95.5),
raw_data: vec![1, 2, 3, 4],
nested: InnerData { value: 5, description: Some("Nested Desc".to_string()) },
data_map: map,
};
// --- Basic Field Extraction ---
// Extract the 'name' field (top-level)
let name_value = FieldExtractor::new("name").evaluate(&data)?;
assert_eq!(name_value, FieldScalarValue::String("Example".to_string()));
// Extract the 'active' field (top-level)
let active_value = FieldExtractor::new("active").evaluate(&data)?;
assert_eq!(active_value, FieldScalarValue::Bool(true));
// Extract the 'score' field (Option<f64>, top-level)
let score_value = FieldExtractor::new("score").evaluate(&data)?;
assert_eq!(score_value, FieldScalarValue::Option(Some(Box::new(FieldScalarValue::F64(95.5)))));
// Extract the 'raw_data' field (Vec<u8> handled via serde_bytes, top-level)
let bytes_value = FieldExtractor::new("raw_data").evaluate(&data)?;
assert_eq!(bytes_value, FieldScalarValue::Bytes(vec![1, 2, 3, 4]));
// --- Nested Field Extraction ---
// Extract nested field 'nested.value'
let nested_val_extractor = NestedFieldExtractor::new_from_path(&["nested", "value"])?;
let nested_val = nested_val_extractor.evaluate(&data)?;
assert_eq!(nested_val, FieldScalarValue::I32(5));
// Extract nested Option field 'nested.description'
let nested_desc_extractor = NestedFieldExtractor::new_from_path(&["nested", "description"])?;
let nested_desc = nested_desc_extractor.evaluate(&data)?;
assert_eq!(nested_desc, FieldScalarValue::Option(Some(Box::new(FieldScalarValue::String("Nested Desc".to_string())))));
// Extract field within map value 'data_map.entry1.value'
let map_val1_extractor = NestedFieldExtractor::new_from_path(&["data_map", "entry1", "value"])?;
let map_val1 = map_val1_extractor.evaluate(&data)?;
assert_eq!(map_val1, FieldScalarValue::I32(-10));
// Extract Option field within map value 'data_map.entry2.description'
let map_val2_desc_extractor = NestedFieldExtractor::new_from_path(&["data_map", "entry2", "description"])?;
let map_val2_desc = map_val2_desc_extractor.evaluate(&data)?;
assert_eq!(map_val2_desc, FieldScalarValue::Option(Some(Box::new(FieldScalarValue::String("Second".to_string())))));
// Extract Option field within map value that is None 'data_map.entry1.description'
let map_val1_desc_extractor = NestedFieldExtractor::new_from_path(&["data_map", "entry1", "description"])?;
let map_val1_desc = map_val1_desc_extractor.evaluate(&data)?;
assert_eq!(map_val1_desc, FieldScalarValue::Option(None));
// --- Error Cases ---
// Trying to extract a non-existent top-level field returns FieldNotFound
let missing_result = FieldExtractor::new("address").evaluate(&data);
assert!(matches!(missing_result, Err(EvaluateError::FieldNotFound { field_name }) if field_name == "address"));
// Trying to extract a non-existent nested field returns NestedFieldNotFound (with path up to failure)
let missing_nested_extractor = NestedFieldExtractor::new_from_path(&["nested", "bad_field"])?;
let missing_nested_result = missing_nested_extractor.evaluate(&data);
assert!(matches!(
missing_nested_result,
Err(EvaluateError::NestedFieldNotFound { ref path }) if path == &vec!["nested".to_string(), "bad_field".to_string()]
));
// Trying to extract from a non-existent map key returns NestedFieldNotFound (with path up to failure)
let missing_map_key_extractor = NestedFieldExtractor::new_from_path(&["data_map", "missing_key", "value"])?;
let missing_map_key_result = missing_map_key_extractor.evaluate(&data);
assert!(matches!(
missing_map_key_result,
Err(EvaluateError::NestedFieldNotFound { ref path }) if path == &vec!["data_map".to_string(), "missing_key".to_string(), "value".to_string()]
));
// Trying to extract a non-existent field within a valid map entry returns NestedFieldNotFound (with path up to failure)
let missing_map_inner_extractor = NestedFieldExtractor::new_from_path(&["data_map", "entry1", "bad_field"])?;
let missing_map_inner_result = missing_map_inner_extractor.evaluate(&data);
assert!(matches!(
missing_map_inner_result,
Err(EvaluateError::NestedFieldNotFound { ref path }) if path == &vec!["data_map".to_string(), "entry1".to_string(), "bad_field".to_string()]
));
// Trying to extract a non-scalar field (struct) itself returns UnsupportedType
let nested_struct_extractor = NestedFieldExtractor::new_from_path(&["nested"])?;
let nested_struct_result = nested_struct_extractor.evaluate(&data);
assert!(matches!(nested_struct_result, Err(EvaluateError::UnsupportedType { .. })));
// Trying to extract a non-scalar field (map) itself returns UnsupportedType
let map_extractor = NestedFieldExtractor::new_from_path(&["data_map"])?;
let map_result = map_extractor.evaluate(&data);
assert!(matches!(map_result, Err(EvaluateError::UnsupportedType { .. })));
Ok(())
}
Supported Types
The final target of the extraction path must be one of the following types
(or an Option
thereof), which can be extracted as FieldScalarValue
variants:
bool
i8
,i16
,i32
,i64
,i128
u8
,u16
,u32
,u64
,u128
f32
,f64
char
String
,&str
Vec<u8>
(requires#[serde(with = "serde_bytes")]
on the field)- Unit (
()
)
Attempting to extract a field path that ultimately points to other types like nested structs,
sequences (except Vec<u8
with serde_bytes
), maps, or enums with data will result in an
EvaluateError::UnsupportedType
. Similarly, if any intermediate part of the path
(e.g., middle
in top.middle.leaf
) is not a struct or a map, extraction will fail.
Note: While Option<Scalar>
fields can be extracted directly (yielding FieldScalarValue::Option(Some(scalar))
or FieldScalarValue::Option(None)
), traversing through an Option
to access fields within the Some
variant
(e.g., opt_struct.inner_field
) is currently not supported. The extraction path must target the Option
itself.
Dependencies
~0.3–1MB
~21K SLoC