Home > Blockchain >  rust: adding a field to an existing struct with serde_json
rust: adding a field to an existing struct with serde_json

Time:05-11

I have a pre-defined struct

use serde::{Deserialize, Serialize};

#[derive(Debug, Serialize, Deserialize)]
struct Foo<T> 
where T: Serialize
{
    foo: T
}

struct Bar{
    a: String
}

struct Bar2{
    b: String
}

fn main() -> Result<()>
{
    let a1 = Bar {a: "something".to_owned(),};
    let a2 = Bar {a: "something2".to_owned(),};
    let a_vec: Vec<Bar> = vec![a1, a2];
    let b = Bar2 {b: "something"}
    let b_vec: Vec<Bar2> = vec![b];
    //let foo = Foo {foo: vec![a_vec,b_vec]}

}

How can I put both struct under Foo or is it possible to first serialize Bar to json and add Bar2 as string literals? The result would be a json

{"foo": [{"a": "something"}, {"a": "something2"}], "b": "something"}

CodePudding user response:

You can get this serialized structure by storing both Foo and Bar2 in another struct and merge them together with #[serde(flatten)]. (playground):

use serde::{Deserialize, Serialize};

#[derive(Debug, Serialize, Deserialize)]
struct Foo<T>
where
    T: Serialize,
{
    foo: T,
}

#[derive(Debug, Serialize)]
struct Bar {
    a: String,
}

#[derive(Debug, Serialize)]
struct Bar2 {
    b: String,
}

#[derive(Debug, Serialize)]
struct Outer<T: Serialize> {
    #[serde(flatten)]
    field_1: Foo<T>,
    #[serde(flatten)]
    field_2: Bar2,
}

fn main() {
    let a1 = Bar {
        a: "something".to_owned(),
    };
    let a2 = Bar {
        a: "something2".to_owned(),
    };
    let a_vec: Vec<Bar> = vec![a1, a2];
    let b = Bar2 {
        b: "something".to_owned(),
    };

    let o = Outer {
        field_1: Foo { foo: a_vec },
        field_2: b,
    };

    println!("{}", serde_json::to_string(&o).unwrap());
}
{"foo":[{"a":"something"},{"a":"something2"}],"b":"something"}

If instead by "no modification of struct" you meant by only serializing Foo and just modifying T, then no its not possible to get that output with serde directly. You'd have to do your proposed method by serializing into Values and merging them yourself.

CodePudding user response:

I see two problems with your code:

  • You haven't derived Serialize for Bar and Bar2.
  • You're trying to put a_vec and b_vec into a vector, which you can't do because they're of different type.

The latter problem can be solved by dynamic dispatch. Instead of putting a_vec into the Vec, you put &a_vec as &dyn Serialize, i.e. just give it a reference to some serializable object. Slight trouble is that this won't work with serde::Serialize because it's not object safe. This is what erased_serde is for:

let foo = Foo::<Vec<&dyn erased_serde::Serialize>> {
    foo: vec![&a_vec, &b_vec],
};
println!("{}", serde_json::to_string_pretty(&foo).unwrap());

will work fine. But it will output

{"foo":[[{"a":"something"},{"a":"something2"}],[{"b":"something"}]]}

which is not what you wanted?

To get the output you wanted, i.e. serialize Foo and add a field to it, you could use #[serde(flatten)]:

let foo = Foo { foo: a_vec };
#[derive(Serialize)]
struct Merge<T1: Serialize, T2: Serialize> {
    #[serde(flatten)]
    f1: T1,
    #[serde(flatten)]
    f2: T2,
}
let out = Merge { f1: &foo, f2: &b };
println!("{}", serde_json::to_string_pretty(&out).unwrap());

The output of serializing Merge both the fields from T1 and T2.

  • Related