serde_cursor
This crate allows you to declaratively specify how to fetch the desired parts of a serde-compatible data format efficiently, without loading it all into memory, using a jq-like language.
= "0.1"
Examples
The Cursor! macro makes it extremely easy to extract nested fields from data.
Get version from Cargo.toml
use Cursor;
let data = r#"
[workspace.package]
version = "0.1"
"#;
let version: String = >?.0;
assert_eq!;
Cursor!(workspace.package.version) is the magic juice - this type-macro expands to a type that implements Deserialize.
Without serde_cursor:
Pain and suffering…
use Deserialize;
let data = r#"
[workspace.package]
version = "0.1"
"#;
let version = ?.workspace.package.version;
Get names of all dependencies from Cargo.lock
The wildcard .* accesses every element in an array:
use Cursor;
let file = r#"
[[package]]
name = "serde"
[[package]]
name = "rand"
"#;
let packages: = >?.0;
assert_eq!;
Without serde_cursor:
use Deserialize;
let file = r#"
[[package]]
name = "serde"
[[package]]
name = "rand"
"#;
let packages = ?
.package
.into_iter
.map
.;
serde_cursor vs serde_query
serde_query also implements jq-like queries, but more verbosely.
Single query
serde_cursor:
use Cursor;
let data = r#"{ "commits": [{"author": "Ferris"}] }"#;
let authors: = >?.0;
serde_query:
use Deserialize;
let data = r#"{ "commits": [{"author": "Ferris"}] }"#;
let data: Data = from_str?;
let authors = data.authors;
Storing queries in a struct
serde_cursor:
use Deserialize;
use Cursor;
let data = r#"{ "count": 1, "commits": [{"author": "Ferris"}] }"#;
let data: Data = from_str?;
serde_query:
use Deserialize;
let data = r#"{ "count": 1, "commits": [{"author": "Ferris"}] }"#;
let data: Data = from_str?;
serde_with integration
If feature = "serde_with" is enabled, Cursor will implement serde_with::DeserializeAs and serde_with::SerializeAs,
meaning you can use it with the #[serde_as] attribute:
use ;
use Cursor;
let toml: CargoToml = from_str?;
assert_eq!;
assert_eq!;
Great error messages
When deserialization fails, you get the exact path of where the failure occurred.
use Cursor;
let data = json!;
let result = >;
let err = result.unwrap_err.to_string;
assert_eq!;
How does it work?
The Cursor! macro is a “type-level” parser. It takes your jq-like query and transforms it into a nested, recursive type that implements serde::Deserialize.
Consider this query, which gets the first dependency of every dependency in Cargo.toml:
Cursor!
For this Cargo.lock, it would extract ["libc", "find-msvc-tools"]:
[[]]
= "android_system_properties"
= ["libc"]
[[]]
= "cc"
= ["find-msvc-tools", "shlex"]
That macro is expanded into a Cursor type, which implements Deserialize and Serialize:
,
,
,
>,
>,
>,
>
The above is essentially an equivalent to:
vec!
Except it exists entirely in the type system.
Each time the Deserialize::deserialize() function is called, the first element of the type-level list is removed,
and the rest of the list is passed to the Deserialize trait, again.
This happens until the list is exhausted, in which case we finally get to the type of the field - the String in the above example,
and finally call Deserialize::deserialize() on that, to finish things off.