{"id":15564051,"url":"https://github.com/eigenein/kv-derive","last_synced_at":"2025-07-31T05:11:01.844Z","repository":{"id":41050874,"uuid":"492895995","full_name":"eigenein/kv-derive","owner":"eigenein","description":"Derive struct conversions from and to key-value vectors","archived":false,"fork":false,"pushed_at":"2022-12-21T12:23:15.000Z","size":92,"stargazers_count":4,"open_issues_count":0,"forks_count":1,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-07-09T21:11:06.204Z","etag":null,"topics":["derive","key-value","proc-macro","rust","rust-crate"],"latest_commit_sha":null,"homepage":"https://lib.rs/crates/kv-derive","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/eigenein.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null},"funding":{"github":["eigenein"],"custom":["https://www.paypal.me/eigenein","https://bunq.me/eigenein","https://money.yandex.ru/to/410011693851843"]}},"created_at":"2022-05-16T15:32:54.000Z","updated_at":"2025-01-17T15:27:00.000Z","dependencies_parsed_at":"2023-01-30T03:30:45.994Z","dependency_job_id":null,"html_url":"https://github.com/eigenein/kv-derive","commit_stats":null,"previous_names":[],"tags_count":30,"template":false,"template_full_name":null,"purl":"pkg:github/eigenein/kv-derive","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/eigenein%2Fkv-derive","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/eigenein%2Fkv-derive/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/eigenein%2Fkv-derive/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/eigenein%2Fkv-derive/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/eigenein","download_url":"https://codeload.github.com/eigenein/kv-derive/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/eigenein%2Fkv-derive/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":266465450,"owners_count":23933125,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-07-22T02:00:09.085Z","response_time":66,"last_error":null,"robots_txt_status":null,"robots_txt_updated_at":null,"robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["derive","key-value","proc-macro","rust","rust-crate"],"created_at":"2024-10-02T16:35:11.120Z","updated_at":"2025-07-22T09:33:43.506Z","avatar_url":"https://github.com/eigenein.png","language":"Rust","funding_links":["https://github.com/sponsors/eigenein","https://www.paypal.me/eigenein","https://bunq.me/eigenein","https://money.yandex.ru/to/410011693851843"],"categories":[],"sub_categories":[],"readme":"# `kv-derive`\n\nDerive `struct` conversions from and to string key-value vectors using [`ToString`](https://doc.rust-lang.org/std/string/trait.ToString.html) and [`FromStr`](https://doc.rust-lang.org/std/str/trait.FromStr.html).\n\n[![Crates.io](https://img.shields.io/crates/v/kv-derive)](https://crates.io/crates/kv-derive)\n[![Last commit](https://img.shields.io/github/last-commit/eigenein/kv-derive)](https://github.com/eigenein/kv-derive/commits/master)\n[![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/eigenein/kv-derive/check.yaml?branch=main)](https://github.com/eigenein/kv-derive/actions)\n![License: MIT](https://img.shields.io/crates/l/kv-derive)\n\n## Examples\n\nAny type that implements [`std::string::ToString`] and/or [`std::str::FromStr`] supported as a field type:\n\n### `#[derive(IntoVec)]`\n\n```rust\nuse kv_derive::prelude::*;\nuse kv_derive::IntoVec;\n\n#[derive(IntoVec)]\nstruct Foo {\n    bar: i32,\n    qux: String,\n}\n\nlet foo = Foo { bar: 42, qux: \"qux\".into() };\nassert_eq!(foo.into_vec(), vec![\n    (\"bar\".into(), \"42\".into()),\n    (\"qux\".into(), \"qux\".into()),\n]);\n```\n\n### `#[derive(FromIter)]`\n\n```rust\nuse kv_derive::prelude::*;\nuse kv_derive::FromIter;\n\n#[derive(FromIter, Debug, PartialEq)]\nstruct Foo {\n    #[kv(default())]\n    bar: i32,\n    \n    #[kv(default())]\n    qux: String,\n}\n\nlet actual = Foo::from_iter(vec![(\"bar\", \"42\"), (\"qux\", \"quuux\")]).unwrap();\nlet expected = Foo { bar: 42, qux: \"quuux\".into() };\nassert_eq!(actual, expected);\n```\n\n`#[derive(FromIter)]` requires that you specify `#[kv(default(…))]` attribute on each field, because it needs to know what to do when the key is missing in the input.\n\n### `#[derive(FromMapping)]`\n\n```rust\nuse std::collections::HashMap;\n\nuse kv_derive::prelude::*;\nuse kv_derive::FromMapping;\n\n#[derive(FromMapping, Debug, PartialEq)]\nstruct Foo {\n    bar: i32,\n    qux: String,\n}\n\nlet mapping = HashMap::from([(\"bar\", \"42\"), (\"qux\", \"quuux\")]);\nlet actual = Foo::from_mapping(\u0026mapping).unwrap();\nlet expected = Foo { bar: 42, qux: \"quuux\".into() };\nassert_eq!(actual, expected);\n```\n\nHere `#[kv(default(…))]` is not required, and missing key causes the error:\n\n```rust\nuse std::collections::HashMap;\n\nuse kv_derive::prelude::*;\nuse kv_derive::FromMapping;\n\n#[derive(FromMapping, Debug, PartialEq)]\nstruct Foo {\n    bar: i32,\n    qux: String,\n}\n\nlet mapping = HashMap::from([(\"bar\", \"42\")]);\nlet actual = Foo::from_mapping(\u0026mapping);\nassert_eq!(actual, Err(kv_derive::error::Error::MissingKey(\"qux\")));\n```\n\n## Customizing fields\n\n### Optional fields\n\nWith `#[kv(optional)]` the macro expects that the fields are wrapped with [`std::option::Option`], and skips `None` values:\n\n```rust\nuse kv_derive::prelude::*;\nuse kv_derive::IntoVec;\n\n#[derive(IntoVec)]\nstruct Foo {\n    #[kv(optional)]\n    bar: Option\u003ci32\u003e,\n    \n    #[kv(optional)]\n    qux: Option\u003ci32\u003e,\n}\n\nlet foo = Foo { bar: Some(42), qux: None };\nassert_eq!(foo.into_vec(), vec![(\"bar\".into(), \"42\".into())]);\n```\n\nNote that the **both** `#[kv(optional)]` and [`std::option::Option`] type are needed here, because technically you could omit `#[kv(optional)]` and implement [`std::string::ToString`] on a custom `Option\u003cT\u003e` to handle `None` values manually.\n\nFor `#[derive(FromIter)]` this also ensures that [`std::str::FromStr`] is called on `T` and not on `Option\u003cT\u003e`:\n\n```rust\nuse kv_derive::prelude::*;\nuse kv_derive::FromIter;\n\n#[derive(FromIter, Debug, PartialEq)]\nstruct Foo {\n    #[kv(default(), optional)]\n    bar: Option\u003ci32\u003e,\n    \n    #[kv(default(), optional)]\n    qux: Option\u003ci32\u003e,\n    \n    #[kv(default(value = \"Some(42)\"), optional)]\n    quux: Option\u003ci32\u003e,\n}\n\nlet actual = Foo::from_iter(vec![(\"bar\", \"42\")]).unwrap();\nlet expected = Foo { bar: Some(42), qux: None, quux: Some(42) };\nassert_eq!(actual, expected);\n```\n\n### Default values\n\n`#[kv(default())]` implies that the type implements [`std::default::Default`]. But you can also specify a custom default value with #[kv(default(value = \"\u003cexpression\u003e\"))]:\n\n```rust\nuse std::collections::HashMap;\n\nuse kv_derive::prelude::*;\nuse kv_derive::FromMapping;\n\n#[derive(FromMapping, Debug, PartialEq)]\nstruct Foo {\n    #[kv(default())]\n    bar: i32,\n    \n    #[kv(default(value = \"42\"))]\n    qux: i32,\n    \n    #[kv(default(), optional)]\n    quux: Option\u003ci32\u003e,\n    \n    #[kv(default(value = \"Some(100500)\"), optional)]\n    quuux: Option\u003ci32\u003e,\n}\n\nlet foo = Foo::from_mapping(\u0026HashMap::\u003cString, String\u003e::new()).unwrap();\nassert_eq!(foo, Foo { bar: 0, qux: 42, quux: None, quuux: Some(100500) });\n```\n\n### Renaming fields with `#[kv(rename = …)]`\n\nUses the specified key instead of the identifier:\n\n```rust\nuse kv_derive::prelude::*;\nuse kv_derive::IntoVec;\n\n#[derive(IntoVec)]\nstruct Foo {\n    #[kv(rename = \"qux\")]\n    bar: i32,\n}\n\nlet foo = Foo { bar: 42 };\nassert_eq!(foo.into_vec(), vec![(\"qux\".into(), \"42\".into())]);\n```\n\n### Convert to and from another representation\n\nHere's an example how you could represent a boolean value with an `i32`:\n\n```rust\nuse std::collections::HashMap;\n\nuse kv_derive::prelude::*;\nuse kv_derive::{IntoVec, FromIter, FromMapping};\n\n#[derive(IntoVec, FromIter, FromMapping, PartialEq, Debug)]\nstruct Foo {\n    #[kv(\n        default(),\n        collection,\n        into_repr_with = \"|value| value as i32\",\n        from_repr_with = \"|value: i32| kv_derive::result::Result::Ok(value != 0)\",\n    )]\n    bar: Vec\u003cbool\u003e,\n}\n\nassert_eq!(\n    Foo { bar: vec![false, true] }.into_vec(),\n    vec![(\"bar\".into(), \"0\".into()), (\"bar\".into(), \"1\".into())],\n);\nassert_eq!(\n    Foo::from_iter(vec![(\"bar\".into(), \"0\".into()), (\"bar\".into(), \"1\".into())]).unwrap(), \n    Foo { bar: vec![false, true] },\n);\nassert_eq!(\n    Foo::from_mapping(HashMap::from([(\"bar\", \"1\")])).unwrap(),\n    Foo { bar: vec![true] },\n);\n```\n\nIn this case, [`std::string::ToString`] and [`std::str::FromStr`] operate on `i32` rather than `bool`.\n\n### Collection fields\n\n```rust\nuse kv_derive::prelude::*;\nuse kv_derive::IntoVec;\n\n#[derive(IntoVec)]\nstruct Foo {\n    #[kv(collection)]\n    bar: Vec\u003ci32\u003e,\n}\n\nlet foo = Foo { bar: vec![42, 100500] };\nassert_eq!(foo.into_vec(), vec![\n    (\"bar\".into(), \"42\".into()),\n    (\"bar\".into(), \"100500\".into()),\n]);\n```\n\n```rust\nuse kv_derive::prelude::*;\nuse kv_derive::FromIter;\n\n#[derive(FromIter, Debug, PartialEq)]\nstruct Foo {\n    #[kv(collection, default())]\n    bar: Vec\u003ci32\u003e,\n}\n\nlet actual = Foo::from_iter(vec![(\"bar\", \"42\".into()), (\"bar\", \"100500\".into())]).unwrap();\nlet expected = Foo { bar: vec![42, 100500] };\nassert_eq!(actual, expected);\n```\n\n#### Note for `#[derive(FromMapping)]`\n\n`HashMap` or `BTreeMap` cannot contain duplicate keys. However, for consistency, singular values are properly converted to [`std::vec::Vec`]s:\n\n```rust\nuse std::collections::HashMap;\n\nuse kv_derive::prelude::*;\nuse kv_derive::FromMapping;\n\n#[derive(FromMapping, Debug, PartialEq)]\nstruct Foo {\n    #[kv(collection)]\n    bar: Vec\u003ci32\u003e,\n}\n\nlet map = HashMap::from([(\"bar\", \"42\")]);\nlet actual = Foo::from_mapping(\u0026map).unwrap();\nlet expected = Foo { bar: vec![42] };\nassert_eq!(actual, expected);\n```\n\n## Flattening\n\n### Simple flattening\n\nIt is possible to «flatten» an inner structure:\n\n```rust\nuse kv_derive::prelude::*;\nuse kv_derive::IntoVec;\n\n#[derive(IntoVec)]\nstruct Bar {\n    qux: i32,\n}\n\n#[derive(IntoVec)]\nstruct Foo {\n    #[kv(flatten())]\n    bar: Bar,\n}\n\nlet foo = Foo { bar: Bar { qux: 42 } };\nassert_eq!(foo.into_vec(), vec![(\"qux\".into(), \"42\".into())]);\n```\n\nNote that the macro doesn't check for possible duplicate keys in outer and inner structures.\n\nIt's **not** possible to derive [`FromIter`](crate::prelude::FromIter) for a structure with a flattened field. However, it works for `#[derive(FromMapping)]`:\n\n```rust\nuse std::collections::HashMap;\n\nuse kv_derive::prelude::*;\nuse kv_derive::FromMapping;\n\n#[derive(FromMapping, Debug, PartialEq)]\nstruct Inner {\n    bar: i32,\n}\n\n#[derive(FromMapping, Debug, PartialEq)]\nstruct Outer {\n    #[kv(flatten())]\n    inner: Inner,\n}\n\nlet map = HashMap::from([(\"bar\", \"42\")]);\nlet actual = Outer::from_mapping(\u0026map).unwrap();\nlet expected = Outer { inner: Inner { bar: 42 } };\nassert_eq!(actual, expected);\n```\n\n### Prefixed flattening\n\nIt's also possible to prefix all the inner fields with a same prefix:\n\n```rust\nuse kv_derive::prelude::*;\nuse kv_derive::IntoVec;\n\n#[derive(IntoVec)]\nstruct Bar {\n    qux: i32,\n}\n\n#[derive(IntoVec)]\nstruct Foo {\n    #[kv(flatten(prefix = \"bar::\"))]\n    bar: Bar,\n}\n\nlet foo = Foo { bar: Bar { qux: 42 } };\nassert_eq!(foo.into_vec(), vec![(\"bar::qux\".into(), \"42\".into())]);\n```\n\nAnd back:\n\n```rust\nuse std::collections::HashMap;\n\nuse kv_derive::prelude::*;\nuse kv_derive::FromMapping;\n\n#[derive(FromMapping, Debug, PartialEq)]\nstruct Inner {\n    bar: i32,\n}\n\n#[derive(FromMapping, Debug, PartialEq)]\nstruct Outer {\n    #[kv(flatten(prefix = \"inner::\"))]\n    inner: Inner,\n}\n\nlet map = HashMap::from([(\"inner::bar\", \"42\")]);\nlet actual = Outer::from_mapping(\u0026map).unwrap();\nlet expected = Outer { inner: Inner { bar: 42 } };\nassert_eq!(actual, expected);\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Feigenein%2Fkv-derive","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Feigenein%2Fkv-derive","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Feigenein%2Fkv-derive/lists"}