Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Can serde deserialize an arbitrary json structure into a recursive enum/tree? #144

Closed
lilith opened this issue Aug 26, 2016 · 4 comments
Closed
Labels

Comments

@lilith
Copy link

lilith commented Aug 26, 2016

For example,

https://github.com/rust-lang-nursery/rustc-serialize/blob/master/src/json.rs#L261-L270

pub enum Json {
    I64(i64),
    U64(u64),
    F64(f64),
    String(string::String),
    Boolean(bool),
    Array(self::Array),
    Object(self::Object),
    Null,
}
pub type Array = Vec<Json>;
pub type Object = BTreeMap<string::String, Json>;

I'd like to mix validated and free-form JSON in the same string.

@dtolnay
Copy link
Member

dtolnay commented Aug 26, 2016

Absolutely. Are you looking to use that specific one from rustc-serialize or can you use this one instead which works out of the box? serde_json::Value

You can embed serde_json::Value into serializable structs to deserialize that part of the struct into a recursive enum:

// Deserialize something like `{"id": "x", "body": /* any valid json */}`
#[derive(Serialize, Deserialize)]
struct MyStruct {
    id: String,
    body: serde_json::Value,
}

Then you can either use the enum directly, or if you want you can continue deserializing it further using serde_json::from_value:

let my_struct: MyStruct = serde_json::from_str(j).unwrap();
if my_struct.id == "x" {
    let x: MyOtherStruct = serde_json::from_value(my_struct.body).unwrap();
    /* ... */
}

EDIT: and to deserialize an entire JSON input to a serde_json::Value:

let value: serde_json::Value = serde_json::from_str(j).unwrap();

@lilith
Copy link
Author

lilith commented Aug 27, 2016

Thank you! 10 hours of reading docs didn't let me find this. Do you know if it's possible to 'pause' deserialization, to (for example), read a version number and swap out the remainder of the parsing/struct? Or does any kind of logic require a full manual implementation?

@dtolnay
Copy link
Member

dtolnay commented Sep 23, 2016

I filed serde-rs/serde-rs.github.io#18 to add an example that uses serde_json::Value.

Any logic more advanced than what is supported by the built-in attributes is going to require implementing Deserialize yourself. In this case if you have something like {"version": 1, "value": (value of type 1)} and {"version": 2, "value": (value of type 2)} then you can deserialize to a struct that has version: u8, value: serde_json::Value, then look at the version and continue deserializing using serde_json::from_value with the correct type.

If you can say a bit more about what the JSON looks like and what you need the Rust data structure to look like, I can flesh out some of the details and possibly use it as the website example because this could be a pretty common use case.

@lborg019
Copy link

Thaaaanks!
I'm parsing an abstract syntax tree produced from a python program. The json has an array with varied objects, and depending on the type, I have to parse it in a different way.
The json looks something like this:

{
    "ast_type": "Module",
    "body": [
        { "ast_type": "Assign",},
        { "ast_type": "FunctionDef",},
        { "ast_type": "Expr",}, 
    ]
}

(I omitted the rest of the information pertinent to each "ast_type". What matters is that they have different structures).

In order to parse it, I programmed a struct for the json, knowing that the body was going to be an array of unknown object structure:

#[derive(Serialize, Deserialize, Debug)]
struct PyBody {
    ast_type: String,
    col_offset: u8,
    lineno: u8,
    //targets: Vec<PyTarget>
}

#[derive(Serialize, Deserialize, Debug)]
struct PyFunction {
    ast_type: String,
    body: Vec<serde_json::Value>
}

For each PyBody "ast_type" I programmed a different struct: PyAssign, PyFunctionDef, PyExpr (omitted here), and this is how the rest of the program looks like

fn read_code_from_file<P: AsRef<Path>>(path: P) -> Result<PyFunction, Box<Error>> {
    // Open the file in read-only mode.
    let file = File::open(path)?;

    // Read the JSON contents of the file as an instance of `User`.
    let first_pass = serde_json::from_reader(file)?;

    // Return the `User`.
    Ok(first_pass)
}

fn main() {
    let nodes = read_code_from_file("../client/file1.json").unwrap();
    println!("Parsed {:?} nodes:\n", nodes.body.len());
    
    //println!("{:#?}", nodes.body[0]);

    //parse each node
    for i in 0..nodes.body.len()
    {
        //parse each node
        let n: PyBody = serde_json::from_value(nodes.body[i].clone()).unwrap();
        match n.ast_type.as_ref() {
            "Assign" => {
                //println!("parsing Assign node:");
                let assign: PyAssign = serde_json::from_value(nodes.body[i].clone()).unwrap();
                println!("{:?}", assign.ast_type);
            },
            "FunctionDef" => {
                //println!("parsing FunctionDef node:");
                let function_def: PyFunctionDef = serde_json::from_value(nodes.body[i].clone()).unwrap();
                println!("{:?}, {:?}", function_def.ast_type, function_def.name);
            },
            "Expr" => { 
                //println!("parsing Expr node:");
                let expr: PyExpr = serde_json::from_value(nodes.body[i].clone()).unwrap();
                println!("{:?}", expr.ast_type);
            },
            _ => println!("matched unknown node, no action taken: {:?}", n.ast_type)
        }
        //println!("{:?}", n);
    }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Development

No branches or pull requests

3 participants