3

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?

Neuron
  • 5,141
  • 5
  • 38
  • 59
Harindaka
  • 4,658
  • 8
  • 43
  • 62
  • AFAIK, you cannot use concat! for what you want, as it will produce a string starting with the characters 'r' and '#', not actually espacing anothing how you want. Also, include! will include actual files, which is definitely not what you want to do here. Why did you use that macro? Did you plan on creating a file that has an escaped text in it and then parse it again? – Jan Hohenheim Nov 15 '17 at 15:51
  • @JanNilsFerner Thank you for that insight. I edited my answer to remove include! and improved concat! usage a bit. Still doesn't seem to work though. – Harindaka Nov 15 '17 at 16:01
  • That clears things up. Your approach will unfortunately still not work, see my answer for details on why. – Jan Hohenheim Nov 15 '17 at 16:27

2 Answers2

6

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.

Jan Hohenheim
  • 3,552
  • 2
  • 17
  • 42
3

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)
}
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
  • Note that you don't need to include `serde` itself, only `serde_json`. Also, OP wants to work with a `&str`, not a `serde_json::Value`, although he probably should rethink that. – Jan Hohenheim Nov 15 '17 at 16:26
  • @JanNilsFerner the `extern crate serde;` is defensive programming (since it's needed for serde_derive), so I just always include it once I have any other serde crate in play. – Shepmaster Nov 15 '17 at 16:28
  • Fair enough, that sounds reasonable. – Jan Hohenheim Nov 15 '17 at 16:36
  • @JanNilsFerner This is what I ended up doing. Also I included the serde json! macro within my own to achieve my objective. Only negative I see now is that I have to do #[macro_use] on extern crate serde_json in addition to doing the same on my crate as both macros are required. Guess I'd have to live with that for now. Thank you for all your support. – Harindaka Nov 15 '17 at 17:17