最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

Rust macro to turn a struct into a hashmap - Stack Overflow

programmeradmin6浏览0评论

I'm writing a testing tool, this needs to produce a test "job" with a large number of different fields, some of which are preset, some of which are read in and some of which are randomly generated. At the end the tool needs to encode all of these fields.

As you would expect I am using a struct to take all these fields, ensure the correct types and implement default values. However, while there are a large number of fields there aren't many different types i.e. I have a ~100 fields but the the only types used for the values are string, i32, std::net::Ipaddr and chrono::naive::NaiveDateTime.

Therefore for the encoding step it would be extremely helpful to turn the struct into either a hashmap of enums (with a variant for each different type) or a number of hashmaps (one for each type of value) so that I can iterate over the keys and values and write logic to encode 4 types rather than 100 or so fields.

I suspect there is a way to do this with macros but I haven't found it. Does one exist? Thanks,

e.g.

enum StringOrI32 {
    StrField(String),
    Int(i32)
}

struct Test {
    a: i32,
    b: i32,
    c: String,
    d: String
}


let a = Test {
    a: 10,
    b: 2,
    ...
    c: "t1".to_string(),
    d: "t2".to_string(),
    ...

};

// turn `a` into

HashMap::from(
    [
        ("a", StringOrI32::Int(10)),
        ("b", StringOrI32::Int(2)),
        ("c", StringOrI32::StrField("t1".to_string())),
        ("d", StringOrI32::StrField("t2".to_string())),
    ]
);

I'm writing a testing tool, this needs to produce a test "job" with a large number of different fields, some of which are preset, some of which are read in and some of which are randomly generated. At the end the tool needs to encode all of these fields.

As you would expect I am using a struct to take all these fields, ensure the correct types and implement default values. However, while there are a large number of fields there aren't many different types i.e. I have a ~100 fields but the the only types used for the values are string, i32, std::net::Ipaddr and chrono::naive::NaiveDateTime.

Therefore for the encoding step it would be extremely helpful to turn the struct into either a hashmap of enums (with a variant for each different type) or a number of hashmaps (one for each type of value) so that I can iterate over the keys and values and write logic to encode 4 types rather than 100 or so fields.

I suspect there is a way to do this with macros but I haven't found it. Does one exist? Thanks,

e.g.

enum StringOrI32 {
    StrField(String),
    Int(i32)
}

struct Test {
    a: i32,
    b: i32,
    c: String,
    d: String
}


let a = Test {
    a: 10,
    b: 2,
    ...
    c: "t1".to_string(),
    d: "t2".to_string(),
    ...

};

// turn `a` into

HashMap::from(
    [
        ("a", StringOrI32::Int(10)),
        ("b", StringOrI32::Int(2)),
        ("c", StringOrI32::StrField("t1".to_string())),
        ("d", StringOrI32::StrField("t2".to_string())),
    ]
);
Share Improve this question edited Mar 26 at 18:16 Pioneer_11 asked Mar 26 at 18:07 Pioneer_11Pioneer_11 1,3222 gold badges10 silver badges32 bronze badges 2
  • Can definitely be done with proc-macros, but probably not straightforward. To simplify you might want to have a single macro to both instantiate Test and generate the hashmap from the same input. The latter can be probably even done with macro_rules! – Eugene Sh. Commented Mar 26 at 18:23
  • You can do it with serde's macros. – Chayim Friedman Commented Mar 26 at 19:28
Add a comment  | 

1 Answer 1

Reset to default 1

You don't need to write a custom macro. You can make use of serde's introspection capabilities combined with the flexibility of the JSON data model:

#[derive(Serialize)]
struct Test {
    // ...fields
}

#[derive(Deserialize, PartialEq, Debug)]
#[serde(untagged)]
enum StringOrI32 {
    StrField(String),
    Int(i32),
}

fn mapify(a: impl Serialize) -> HashMap<String, StringOrI32> {
    // turn the struct into serde_json::Value that holds a map like
    // json!({"a": 10, "b": 2, "c": "t1", "d": "t2"})
    let val = serde_json::to_value(a).unwrap();
    // turn that map into the desired HashMap
    serde_json::from_value(val).unwrap()
}

Playground

Note on unwraps: serde returns errors for two reasons: underlying IO error, and the (de)serializer refusing to accept the data, such as when a field contains something other than string or number. The former can't happen when (de)serializing to values, and the latter can only happen if value's type contains fields incompatible with this kind of map, which is a programming bug and warrants a panic.

发布评论

评论列表(0)

  1. 暂无评论