getditto / miniserde

Data structure serialization library with several opposite design goals from Serde

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Miniserde

Prototype of a data structure serialization library with several opposite design goals from Serde.

As a prototype, this library is not a production quality engineering artifact the way Serde is. At the same time, it is more than a proof of concept and should be totally usable for the range of use cases that it targets, which is qualified below.

[dependencies.miniserde_ditto]
git = "https://github.com/getditto/miniserde"
version = "0.2.0-dev"

Example

use miniserde_ditto::{json, Serialize, Deserialize};

#[derive(Serialize, Deserialize, Debug)]
struct Example {
    code: u32,
    message: String,
}

fn main() -> miniserde_ditto::Result<()> {
    let example = Example {
        code: 200,
        message: "reminiscent of Serde".to_owned(),
    };

    let j = json::to_string(&example)?;
    println!("{}", j);

    let out: Example = json::from_str(&j)?;
    println!("{:?}", out);

    Ok(())
}

Here are some similarities and differences compared to Serde.

Different: No monomorphization for way smaller binary size

There are no nontrivial generic methods. All serialization and deserialization happens in terms of trait objects. Thus no code is compiled more than once across different generic parameters. In contrast, serde_json needs to stamp out a fair amount of generic code for each choice of data structure being serialized or deserialized.

Without monomorphization, the derived impls compile lightning fast and occupy very little size in the executable.

Similar: very similar API

This crates shares, API-wise, the same "entry-points" as the serde ecosystem:

  • #[cfg(feature = "miniserde")] // Optional: using feature-gating magic
    use miniserde_ditto::{
        self as serde,
        cbor as serde_cbor,
        json as serde_json,
    };
    
    use serde::{
        Deserialize,
        Error,
        Result,
        Serialize,
    };
    use serde_cbor::{
        // from_reader, /* TODO */
        from_slice,
        to_vec,
        to_writer,
    };
    use serde_json::{
        from_str,
        to_string,
    };

This is to enable "easily" switching between the two "philosophical tradeoffs" (speed vs. binary size) using compile-time flags.

Different: Reduced design

This library does not tackle as expansive of a range of use cases as Serde does. If your use case is not already covered, please use Serde.

Different: No Less recursion

Serde depends on recursion for serialization as well as deserialization. Every level of nesting in your data means more stack usage until eventually you overflow the stack. Some formats set a cap on nesting depth to prevent stack overflows and just refuse to deserialize deeply nested data.

In miniserde_ditto::json neither serialization nor deserialization involves recursion. You can safely process arbitrarily nested data without being exposed to stack overflows. Not even the Drop impl of our json Value type is recursive so you can safely nest them arbitrarily.

On the other hand, miniserde_ditto::cbor deserialization does use recursion. It is capped, so that a deeply nested object (e.g., 256 layers) causes a controlled deserialization error (no stack overflow). This is by design, since it doesn't seem possible to feature a design with:

  • simple traits;
  • no recursion;
  • no unsafe.

Different: No deserialization error messages

When deserialization fails, the error type is a unit struct containing no information. This is a legit strategy and not just laziness. If your use case does not require error messages, good, you save on compiling and having your instruction cache polluted by error handling code. If you do need error messages, then upon error you can pass the same input to serde_json to receive a line, column, and helpful description of the failure. This keeps error handling logic out of caches along the performance-critical codepath.

Different: JSON & CBOR only

The same approach in this library could be made to work for other data formats, but it is not a goal to enable that through what this library exposes.

Different: Less customization

Serde has tons of knobs for configuring the derived serialization and deserialization logic through attributes. Or for the ultimate level of configurability you can handwrite arbitrarily complicated implementations of its traits.

List of supported attributes

  • #[serde(rename)] on variants and/or fields;

  • #[serde(tag = "tag_name")] or #[serde(untagged")] on enums;

  • #[serde(skip{,_{,de}serializing})] on fields;

  • #[serde(with = "serde_bytes")] currently ignored, since a clever design of the library already allows to specialize on sequences of bytes.

  • #[serde(default)] for Option fields only, in which case it is handled by default (attribute is ignored).

    • Extend that to non-Option types.
  • #[serde(skip_serializing_if = "some_condition")] Is currently accepted, but ignored. So only use it as a (missed) optimization.

  • Any other attribute.

If you need anything else, use Serde – it's a great library.


License

Licensed under either of Apache License, Version 2.0 or MIT license at your option.
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in this crate by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.

About

Data structure serialization library with several opposite design goals from Serde

License:Apache License 2.0


Languages

Language:Rust 100.0%