How do I change the following from_str macro to from_json?

Viewed 287

I have the following macro. Note that StringContent is an enum item.

#[macro_export]
macro_rules! from_str {  
    ($json:expr) => {         
        StringContent(String::from($json))
    }
}

which allows me to write code like

from_str!(r#"{
    "appName": "Demo App",
    "appVersion": "1.0",
    "database": {
        "host": "dev.database.com",
        "port": 3000
    }
}"#)

Now I want another macro from_json! which allows me to do get rid of the r#""# like so

from_json!({
    "appName": "Demo App",
    "appVersion": "1.0",
    "database": {
        "host": "dev.database.com",
        "port": 3000
    }
})

I've tried the following which does not seem to work

#[macro_export]
macro_rules! from_json {  
    ($t:tt) => {         
        StringContent(String::from(concat!(r#"r#""#, stringify!($t), r#"""# , r#"#"#)))
    }
}

How can I get from_json to work?

2 Answers

Your macro doesn't work because concat! cannot be used to append identifiers to each other in a syntactically sound way. It instead concatenates identifiers into a string. Yours now looks like "r#\" ~your JSON~ \"#", with the r# and # being literal characters in it.

Your approach won't work until a stabilized, extended concat_idents! is implemented.

You will have to parse the JSON syntax by hand in your macro. For inspiration, take a look at how Serde does it.

serde_json in general seems to fit your use case quite well. If possible, I suggest removing custom implementations of any JSON parsing and use serde_json instead, as it is the de-facto standard choice for all things JSON in Rust.

This is a minimal example of how to convert your JSON into a raw string with serde_json:

#[macro_use]
extern crate serde_json;

fn main() {
    let as_json_value = json!({
        "appName": "Demo App",
        "appVersion": "1.0",
        "database": {
            "host": "dev.database.com",
            "port": 3000
        }
    });
    let as_string = format!("{}", as_json_value);
    println!("{}", as_string);
}

Although you probably will want to rewrite your StringContent enum to be built from a serde_json::Value, as it is already neatly parsed for you.

I'd just use the json macro provided by serde_json, which works with your exact syntax:

#[macro_use]
extern crate serde_json;
extern crate serde;

fn main() {
    let x = json!({
        "appName": "Demo App",
        "appVersion": "1.0",
        "database": {
            "host": "dev.database.com",
            "port": 3000
        }
    });
}

This creates a Value struct. If for some reason you really needed it back as a string, you'd need to re-serialize it using it's Display implementation:

extern crate serde;
#[macro_use]
extern crate serde_json;

struct StringContent(String);

macro_rules! from_json {
    ($x:tt) => {{
        StringContent(json!($x).to_string())
    }}
}

fn main() {
    let x = from_json!({
            "appName": "Demo App",
            "appVersion": "1.0",
            "database": {
                "host": "dev.database.com",
                "port": 3000
            }
        });

    println!("{}", x.0)
}
Related