A Rust port of the addict library. addict
gives you a Dict type whose nested values are easy to read and write, with
automatic creation of intermediate nodes — the same convenience the original
Python module offered, expressed through an idiomatic Rust API.
History: addict began as a Python module by Mats Julian Olsen that subclassed
dictand used attribute access (body.query.filtered.query = ...) to build deeply nested mappings. This repository is a behavioral migration of that module to Rust. Python is referenced here only as historical context; the implementation and test suite are entirely Rust.
Instead of building a deeply nested mapping by hand, you can auto-create the intermediate nodes as you assign a leaf value.
use addict::{Dict, Value};
let mut body = Dict::new();
body.set_path(&["query", "filtered", "query", "match", "description"], "addictive").unwrap();
body.set_path(&["query", "filtered", "filter", "term", "created_by"], "Mats").unwrap();The dict! macro is available for flat literals:
use addict::{dict, Value};
let d = dict! { "a" => 1, "b" => "two" };
assert_eq!(d.get("a"), Some(&Value::Int(1)));Key— a map key:Str,Int,Bool, orTuple(mirrors Python's support for string, integer, and tuple keys).Value— a value:Null,Bool,Int,Float,Str,List,Tuple, or a nestedDict.Dict— an insertion-ordered map with the addict conveniences below.Object/ObjectValue— the plain, non-Dictrepresentation returned byto_object()(the analogue of Python'sto_dict()).
let mut d = Dict::new();
d.set_path(&["a", "b", "c"], vec![Value::Int(1), Value::Int(2), Value::Int(3)]).unwrap();
assert_eq!(d.path(&["a", "b", "c"]).unwrap(), Value::List(vec![Value::Int(1), Value::Int(2), Value::Int(3)]));Reading a missing path returns an empty Dict (like Python's
defaultdict(Dict)), without materializing it into the parent:
let d = Dict::new();
let _ = d.get_or_empty("missing").unwrap(); // Ok(Value::Dict(empty))
assert!(d.is_empty()); // nothing was storedWhen you build a Dict from pairs, nested maps become Dicts and iterables are
cloned — the constructor copies, so later external mutation does not leak in.
set_attr refuses to overwrite names that are native to the API (keys,
items, values, get, update, to_dict, …), returning an
Error::AttributeError. Item-style access (set / set_path) has no such
restriction, so d["keys"]["x"] = 1 works fine.
update merges nested Dicts recursively rather than replacing them:
let mut base = Dict::new();
base.set_path(&["a", "b"], 3).unwrap();
let mut patch = Dict::new();
patch.set_path(&["a", "c"], 4).unwrap();
base.update(&patch);
// base == {'a': {'b': 3, 'c': 4}}or(&other)— analogue ofself | otherror(&other)— analogue ofother | selfior(&other)— analogue ofself |= other
freeze() / unfreeze() recursively toggle a frozen flag. A frozen Dict
raises KeyError on missing-key access and on new-key assignment, but still
allows setting pre-existing keys.
to_object()returns anObject(a regular, non-Dictmap clone).to_json()serializes in insertion order (matching Pythonjson.dumpsdefault separators).to_python_repr()renders the Pythonstr(dict)style, e.g.{'a': {'b': {'c': [1, 2, 3]}}}.
Requires a stable Rust toolchain (install via rustup).
cargo build # build the library
cargo test # run the full test suite
cargo clippy --all-targets
cargo fmt --checkThe test suite in tests/addict_tests.rs ports the original Python unit tests
one-for-one and runs each behavioral case for both the base and a "child"
variant, preserving the original suite's coverage.
MIT — see LICENSE. Original work Copyright (c) 2014 Mats Julian Olsen.