Tristan Storch
Tristan Storch

Reputation: 830

Omit values that are Option::None when encoding JSON with rustc_serialize

I have a struct that I want to encode to JSON. This struct contains a field with type Option<i32>. Let's say

extern crate rustc_serialize;
use rustc_serialize::json;

#[derive(RustcEncodable)]
struct TestStruct {
    test: Option<i32>
}

fn main() {
    let object = TestStruct {
        test: None
    };

    let obj = json::encode(&object).unwrap();

    println!("{}", obj);
}

This will give me the output

{"test": null}

Is there a convenient way to omit Option fields with value None? In this case I would like to have the output

{}

Upvotes: 1

Views: 1646

Answers (3)

bernardn
bernardn

Reputation: 1751

If someone arrives here with the same question like I did, serde has now an option skip_serializing_none to do exactly that.

https://docs.rs/serde_with/1.8.0/serde_with/attr.skip_serializing_none.html

Upvotes: 4

Andrew Straw
Andrew Straw

Reputation: 1346

To omit Option<T> fields, you can create an implementation of the Encodable trait (instead of using #[derive(RustcEncodable)]).

Here I updated your example to do this.

extern crate rustc_serialize;
use rustc_serialize::json::{ToJson, Json};
use rustc_serialize::{Encodable,json};

use std::collections::BTreeMap;

#[derive(PartialEq, RustcDecodable)]
struct TestStruct {
    test: Option<i32>
}

impl Encodable for TestStruct {
    fn encode<S: rustc_serialize::Encoder>(&self, s: &mut S) -> Result<(), S::Error> {
        self.to_json().encode(s)
    }
}

impl ToJson for TestStruct {
    fn to_json(&self) -> Json {
        let mut d = BTreeMap::new();
        match self.test {
            Some(value) => { d.insert("test".to_string(), value.to_json()); },
            None => {},
        }
        Json::Object(d)
    }
}

fn main() {
    let object = TestStruct {
        test: None
    };

    let obj = json::encode(&object).unwrap();

    println!("{}", obj);

    let decoded: TestStruct = json::decode(&obj).unwrap();
    assert!(decoded==object);
}

It would be nice to implement a custom #[derive] macro which does this automatically for Option fields, as this would eliminate the need for such custom implementations of Encodable.

Upvotes: 0

XAMPPRocky
XAMPPRocky

Reputation: 3599

It doesn't seem to be possible by doing purely from a struct, so i converted the struct into a string, and then converted that into a JSON object. This method requires that all Option types be the same type. I'd recommend if you need to have variable types in the struct to turn them into string's first.

field_vec and field_name_vec have to be filled with all fields at compile time, as I couldn't find a way to get the field values, and field names without knowing them in rust at run time.

extern crate rustc_serialize;

use rustc_serialize::json::Json;

fn main() {


    #[derive(RustcEncodable)]
    struct TestStruct {
        test: Option<i32>
    }

    impl TestStruct {
        fn to_json(&self) -> String {
            let mut json_string = String::new();
            json_string.push('{');

            let field_vec = vec![self.test];
            let field_name_vec = vec![stringify!(self.test)];
            let mut previous_field = false;
            let mut count = 0;
            for field in field_vec {
                if previous_field {
                    json_string.push(',');
                }
                match field {
                    Some(value) => {
                        let opt_name = field_name_vec[count].split(". ").collect::<Vec<&str>>()[1];
                        json_string.push('"');
                        json_string.push_str(opt_name);
                        json_string.push('"');
                        json_string.push(':');
                        json_string.push_str(&value.to_string());
                        previous_field = true;
                    },
                    None => {},
                }
                count += 1;
            }
            json_string.push('}');
            json_string
        }
    }

    let object = TestStruct {
        test: None
    };

    let object2 = TestStruct {
        test: Some(42)
    };

    let obj = Json::from_str(&object.to_json()).unwrap();
    let obj2 = Json::from_str(&object2.to_json()).unwrap();

    println!("{:?}", obj);
    println!("{:?}", obj2);
}

Upvotes: 0

Related Questions