How to get renamed enum name from enum value? - rust

So I have
#[derive(Deserialize, Clone, Debug, Copy)]
pub enum ComparisonOperators {
#[serde(rename = "=")]
EQ,
#[serde(rename = "!=")]
NEQ,
#[serde(rename = ">")]
GT,
#[serde(rename = ">=")]
GE,
#[serde(rename = "<")]
LT,
#[serde(rename = "<=")]
LE,
}
I want to get from let i = ComparisonOperators::GE to ">=". Can I do this by not adding a mapper?

You can add the Serialize tag, and then use the serde_json to serialize to a String as per your attributes renaming:
use serde::{Deserialize, Serialize};
use serde_json; // 1.0.78
#[derive(Deserialize, Serialize, Clone, Debug, Copy)]
pub enum ComparisonOperators {
#[serde(rename = "=")]
EQ,
#[serde(rename = "!=")]
NEQ,
#[serde(rename = ">")]
GT,
#[serde(rename = ">=")]
GE,
#[serde(rename = "<")]
LT,
#[serde(rename = "<=")]
LE,
}
fn main() {
let tag = serde_json::to_string(&ComparisonOperators::EQ).unwrap();
println!("{tag}");
}
Playground
You can even implement display for your enum based on that:
impl fmt::Display for ComparisonOperators {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", serde_json::to_string(self).unwrap())
}
}
Playground
Remember that Display gives you a ToString implementation for free.

Related

Serde tag = x, but keep the tag in the struct

I'm trying to de-serialise JSON to a Rust struct with enum variants using internally tagged JSON (https://serde.rs/enum-representations.html).
I want to store the tag of the variant inside the struct - currently serde stores this data in attributes.
Can this be done keeping the tag key inside the struct?
The methods I have tried:
A. #[serde(untagged)]
This works but I want to avoid it because of the performance hit of searching for a pattern match.
B. #[serde(tag = "f_tag")]
This does not work and results in "duplicate field `f_tag`".
Both the serde attribute and the enum variant rename try to use the same key.
I do not want to place #[serde(rename = "f_tag")] under Uni as this defines the tag key in the parent enum. (I want child structs to have the same tag key anywhere they are contained inside a parent enum).
Example:
https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=584c3f32488a1a27f47dac9a0e81d31c
use serde::{Deserialize, Serialize};
use serde_json::json;
use serde_json::{Result, Value};
#[derive(Serialize, Deserialize, Debug)]
struct S1 {
f1: String,
f2: Uni,
}
#[derive(Serialize, Deserialize, Debug)]
#[serde(tag = "f_tag")]
// #[serde(untagged)]
enum Uni {
String(String),
// #[serde(rename = "f_tag")]
S2(S2),
// #[serde(rename = "f_tag")]
S3(S3),
}
#[derive(Serialize, Deserialize, Debug)]
struct S2 {
f_tag: S2Tag,
f2_s2: bool,
}
#[derive(Serialize, Deserialize, Debug)]
struct S3 {
f_tag: S3Tag,
f2_s3: bool,
}
#[derive(Serialize, Deserialize, Debug)]
enum S2Tag {
#[serde(rename = "s2")]
S2,
}
#[derive(Serialize, Deserialize, Debug)]
enum S3Tag {
#[serde(rename = "s3")]
S3,
}
fn main() {
let s1 = S1 {
f1: "s1.f1".into(),
f2: Uni::S2(S2 {
f_tag: S2Tag::S2,
f2_s2: true,
}),
};
let j = serde_json::to_string(&s1).unwrap();
dbg!(&j);
let s1_2: S1 = serde_json::from_str(&j).unwrap();
dbg!(s1_2);
}
{
"f1": "s1.f1",
"f2": {
// Issue: serde(tag = x) uses the name of the enum here.
"f_tag": "S2",
"f_tag": "s2",
"f2_s2": true
}
}

How to serialise and deserialise BTreeMaps with arbitrary key types?

This example code:
use std::collections::BTreeMap;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
struct Foo {
bar: String,
baz: Baz
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
enum Baz {
Quux(u32),
Flob,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
struct Bish {
bash: u16,
bosh: i8
}
fn main() -> std::io::Result<()> {
let mut btree: BTreeMap<Foo, Bish> = BTreeMap::new();
let foo = Foo {
bar: "thud".to_string(),
baz: Baz::Flob
};
let bish = Bish {
bash: 1,
bosh: 2
};
println!("foo: {}", serde_json::to_string(&foo)?);
println!("bish: {}", serde_json::to_string(&bish)?);
btree.insert(foo, bish);
println!("btree: {}", serde_json::to_string(&btree)?);
Ok(())
}
gives the runtime output/error:
foo: {"bar":"thud","baz":"Flob"}
bish: {"bash":1,"bosh":2}
Error: Custom { kind: InvalidData, error: Error("key must be a string", line: 0, column: 0) }
I've googled this, and found that the problem is that the serialiser would be trying to write:
{{"bar":"thud","baz":"Flob"}:{"bash":1,"bosh":2}}}
which is not valid JSON, as keys must be strings.
The internet tells me to write custom serialisers.
This is not a practical option, as I have a large number of different non-string keys.
How can I make serde_json serialise to (and deserialise from):
{"{\"bar\":\"thud\",\"baz\":\"Flob\"}":{"bash":1,"bosh":2}}
for arbitrary non-string keys in BTreeMap and HashMap?
Although OP decided not to use JSON in the end, I have written a crate that does exactly what the original question asked for: https://crates.io/crates/serde_json_any_key. Using it is as simple as a single function call.
Because this is StackOverflow and just a link is not a sufficient answer, here is a complete implementation, combining code from v1.1 of the crate with OP's main function, replacing only the final call to serde_json::to_string:
extern crate serde;
extern crate serde_json;
use serde::{Serialize, Deserialize};
use std::collections::BTreeMap;
mod serde_json_any_key {
use std::any::{Any, TypeId};
use serde::ser::{Serialize, Serializer, SerializeMap, Error};
use std::cell::RefCell;
struct SerializeMapIterWrapper<'a, K, V>
{
pub iter: RefCell<&'a mut (dyn Iterator<Item=(&'a K, &'a V)> + 'a)>
}
impl<'a, K, V> Serialize for SerializeMapIterWrapper<'a, K, V> where
K: Serialize + Any,
V: Serialize
{
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where
S: Serializer
{
let mut ser_map = serializer.serialize_map(None)?;
let mut iter = self.iter.borrow_mut();
// handle strings specially so they don't get escaped and wrapped inside another string
if TypeId::of::<K>() == TypeId::of::<String>() {
while let Some((k, v)) = iter.next() {
let s = (k as &dyn Any).downcast_ref::<String>().ok_or(S::Error::custom("Failed to serialize String as string"))?;
ser_map.serialize_entry(s, &v)?;
}
} else {
while let Some((k, v)) = iter.next() {
ser_map.serialize_entry(match &serde_json::to_string(&k)
{
Ok(key_string) => key_string,
Err(e) => { return Err(e).map_err(S::Error::custom); }
}, &v)?;
}
}
ser_map.end()
}
}
pub fn map_iter_to_json<'a, K, V>(iter: &'a mut dyn Iterator<Item=(&'a K, &'a V)>) -> Result<String, serde_json::Error> where
K: Serialize + Any,
V: Serialize
{
serde_json::to_string(&SerializeMapIterWrapper {
iter: RefCell::new(iter)
})
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
struct Foo {
bar: String,
baz: Baz
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
enum Baz {
Quux(u32),
Flob,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
struct Bish {
bash: u16,
bosh: i8
}
fn main() -> std::io::Result<()> {
let mut btree: BTreeMap<Foo, Bish> = BTreeMap::new();
let foo = Foo {
bar: "thud".to_string(),
baz: Baz::Flob
};
let bish = Bish {
bash: 1,
bosh: 2
};
println!("foo: {}", serde_json::to_string(&foo)?);
println!("bish: {}", serde_json::to_string(&bish)?);
btree.insert(foo, bish);
println!("btree: {}", serde_json_any_key::map_iter_to_json(&mut btree.iter())?);
Ok(())
}
Output:
foo: {"bar":"thud","baz":"Flob"}
bish: {"bash":1,"bosh":2}
btree: {"{\"bar\":\"thud\",\"baz\":\"Flob\"}":{"bash":1,"bosh":2}}
After discovering Rusty Object Notation, I realised that I was pushing a RON-shaped peg into a JSON-shaped hole.
The correct solution was to use JSON for the interface with the outside world, and RON for human-readable local data storage.

Custom mapping for into_group_map in rust

I have the following data structure
pub struct FileContent{
version: u16,
fileTypes: Vec<FileVersion>
}
#[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize)]
pub enum FileVersion {
Cats(String, Vec<Definition>),
Dogs(Vec<Definition>),
Birds(Vec<Definition>),
}
pub struct Definition {
..
}
And I want to parse it into a HashMap<string, Vec> where only FileVersion::Cats are included.
I have the following code:
use itertools::Itertools;
let x = config
.fileTypes
.iter()
.filter_map(|voc| match voc {
FileVersion::Cats(s, v) => Some((s, v)),
_ => None,
})
.into_group_map();
Which gives me a HashMap<string, Vec<Vec>>, but there is really no need for the double vector structure. How can I map this so I get HashMap<string, Vec>?
Would using std::iter::FromIterator be an option?
The following might create the structure that you're looking for:
use std::collections::HashMap;
use std::iter::FromIterator;
#[derive(Clone, PartialEq, Eq, Debug)]
pub enum FileVersion {
Cats(String, Vec<Definition>),
Dogs(Vec<Definition>),
Birds(Vec<Definition>),
}
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct Definition;
fn main() {
let file_types = vec![
FileVersion::Cats(String::from("A"), vec![Definition, Definition]),
FileVersion::Dogs(vec![Definition]),
FileVersion::Cats(String::from("B"), vec![Definition]),
FileVersion::Birds(vec![Definition]),
];
let my_map = HashMap::<_, _>::from_iter(file_types.into_iter().filter_map(|voc| match voc {
FileVersion::Cats(s, v) => Some((s, v)),
_ => None,
}));
}
The value for my_map should then equal {"A": [Definition, Definition], "B": [Definition]}.

Is there a way of making serde_json deserialize strictly?

What I mean is that if 2 objects overlaps on some of the attributes is there a way to try to match all of them?
For example:
use serde::{Serialize, Deserialize};
use serde_json; // 1.0.47; // 1.0.104
#[derive(Serialize, Deserialize, Debug)]
pub struct A {
pub item_1: i32,
pub item_2: i32,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct B {
pub item_1: i32,
pub item_2: i32,
pub item_3: i32,
}
fn main() {
let json_data = r#"{"item_1" : 10, "item_2" : 100, "item_3" : 1000}"#;
if let Ok(data) = serde_json::from_str::<A>(json_data) {
println!("{:?}", data);
} else if let Ok(data) = serde_json::from_str::<B>(json_data) {
println!("{:?}", data);
}
}
Playground
It always succed on A, but I want it to fail because it has extra data so that it falls to B (where it matches exaclty). This is just an example of the problem I am having.
Serde has many attributes to configure this kind of behavior.
One of them is #[serde(deny_unknown_fields)] which does exactly that:
use serde::{Deserialize, Serialize};
use serde_json; // 1.0.47; // 1.0.104
#[derive(Serialize, Deserialize, Debug)]
#[serde(deny_unknown_fields)]
pub struct A {
pub item_1: i32,
pub item_2: i32,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct B {
pub item_1: i32,
pub item_2: i32,
pub item_3: i32,
}
fn main() {
let json_data = r#"{"item_1" : 10, "item_2" : 100, "item_3" : 1000}"#;
if let Ok(data) = serde_json::from_str::<A>(json_data) {
println!("{:?}", data);
} else if let Ok(data) = serde_json::from_str::<B>(json_data) {
println!("{:?}", data);
}
}
outputs:
B { item_1: 10, item_2: 100, item_3: 1000 }
(Permalink to the playground)

How do I serialize and deserialize a remote crate's enum as a number?

I have been trying to setup the following configuration for the serialport crate in Rust with serde, so I can intuitively supply 7 in my config for data_bits, but it will be deserialized as serialport::DataBits::Seven. Unfortunately, it seemingly fails the moment I want it to be a number (7) and not a string (seven).
Test case
cargo.toml
[package]
name = "serde_error"
version = "0.1.0"
authors = ["Jason Miller"]
edition = "2018"
[dependencies]
serialport = "3.3.0"
serde = { version = "1.0", features = ["derive"] }
ron = "0.5.1"
The following results in the error:
6:16: Expected identifier
main.rs
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Debug)]
#[serde(remote = "serialport::DataBits")]
pub enum DataBitsDef {
#[serde(rename = "5")]
Five,
#[serde(rename = "6")]
Six,
#[serde(rename = "7")]
Seven,
#[serde(rename = "8")]
Eight,
}
fn default_data_bits() -> serialport::DataBits {
serialport::DataBits::Eight
}
#[derive(Debug, Serialize, Deserialize)]
pub struct TransceiverSettings {
pub vid: u16,
pub pid: u16,
pub baud_rate: u32,
#[serde(default = "default_data_bits", with = "DataBitsDef")]
pub data_bits: serialport::DataBits,
}
impl Default for TransceiverSettings {
fn default() -> Self {
Self {
vid: 0x2341,
pid: 0x0043,
baud_rate: 115_200,
data_bits: serialport::DataBits::Eight,
}
}
}
const TRX_CONFIG: &str = "
(
vid: 0x2341,
pid: 0x0043,
baud_rate: 9600,
data_bits: 7,
)
";
fn main() {
match ron::de::from_str::<TransceiverSettings>(&TRX_CONFIG) {
Err(e) => eprintln!("{}", e),
Ok(c) => println!("{:?}", c),
}
}
Oddly enough, writing 7 as seven succeeds and returns:
TransceiverSettings { vid: 9025, pid: 67, baud_rate: 9600, data_bits: Seven }
main.rs
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Debug)]
#[serde(remote = "serialport::DataBits")]
pub enum DataBitsDef {
#[serde(rename = "5")]
Five,
#[serde(rename = "6")]
Six,
#[serde(rename = "seven")]
Seven,
#[serde(rename = "8")]
Eight,
}
fn default_data_bits() -> serialport::DataBits {
serialport::DataBits::Eight
}
#[derive(Debug, Serialize, Deserialize)]
pub struct TransceiverSettings {
pub vid: u16,
pub pid: u16,
pub baud_rate: u32,
#[serde(default = "default_data_bits", with = "DataBitsDef")]
pub data_bits: serialport::DataBits,
}
impl Default for TransceiverSettings {
fn default() -> Self {
Self {
vid: 0x2341,
pid: 0x0043,
baud_rate: 115_200,
data_bits: serialport::DataBits::Eight,
}
}
}
const TRX_CONFIG: &str = "
(
vid: 0x2341,
pid: 0x0043,
baud_rate: 9600,
data_bits: seven,
)
";
fn main() {
match ron::de::from_str::<TransceiverSettings>(&TRX_CONFIG) {
Err(e) => eprintln!("{}", e),
Ok(c) => println!("{:?}", c),
}
}
serde_repr
One of the given examples in the serde documentation seems relevant to my case, but I haven't managed to get it working with my setup.
Serialize enum as number
The serde_repr crate provides alternative derive macros that derive the same Serialize and Deserialize traits but delegate to the underlying representation of a C-like enum. This allows C-like enums to be formatted as integers rather than strings in JSON
#[derive(Serialize_repr, Deserialize_repr, PartialEq, Debug)]
#[repr(u8)]
enum SmallPrime {
Two = 2,
Three = 3,
Five = 5,
Seven = 7,
}
Support for #[serde(remote)] is not present in serde_repr 0.1.5. You will need to submit a pull request or issue to add support for it.
Instead, follow the advice in How to transform fields during deserialization using Serde? and How to transform fields during serialization using Serde?:
use serde::{Deserialize, Serialize};
mod shim {
use serde::{de::Error, Deserialize, Deserializer, Serialize, Serializer};
use serialport::DataBits;
pub fn serialize<S>(v: &DataBits, s: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
use DataBits::*;
let v: u8 = match v {
Five => 5,
Six => 6,
Seven => 7,
Eight => 8,
};
v.serialize(s)
}
pub fn deserialize<'de, D>(d: D) -> Result<DataBits, D::Error>
where
D: Deserializer<'de>,
{
use DataBits::*;
match u8::deserialize(d)? {
5 => Ok(Five),
6 => Ok(Six),
7 => Ok(Seven),
8 => Ok(Eight),
o => Err(D::Error::custom(format_args!("Invalid value {}", o))),
}
}
}
#[derive(Debug, Serialize, Deserialize)]
pub struct TransceiverSettings {
pub vid: u16,
pub pid: u16,
pub baud_rate: u32,
#[serde(default = "default_data_bits", with = "shim")]
pub data_bits: serialport::DataBits,
}

Resources