Abhilash Gopalakrishna
Abhilash Gopalakrishna

Reputation: 980

Rust: Serializing a nested json stirng to a BTree<string,string>

I am trying to serialise a nested json to a BTree<string,string>. I will be using specific elements of this collection to bind to different structs as required.

JSON

{
    "data": "some_data",
    "key": "some_key",
    "nestedValue": {
        "timestamp": "0",
        "d1": "d1",
        "d2": "d2",
        "time": 0,
        "secindaryNestedValue": [{
                "d3": "test1",
                "d4": "test2"
            },
            {
                "d3": "test3",
                "d4": "test4"
            }
        ]
    },
    "timestamp": 0
}

I am attempting to serialise this as follows:

    let input: BTreeMap<String, String> = serde_json::from_str(INPUT).unwrap();
    println!("input -> {:?}",input);

I want to get an output as following:

BTree items

Key             Value
data            some_data
key             some_key
nested_value    "{\"d1\":\"d1\",\"d2\":\"d2\",\"time\":0,\"secindaryNestedValue\":[{\"d3\":\"test1\",\"d4\":\"test2\"},{\"d3\":\"test3\",\"d4\":\"test4\"}]}"  
timestamp        0

I am doing this so that my nested jsons can be as generic as possible.

In subsequent operations I will be binding the nested json to a struct using serde as follows using the struct :

use serde_derive::Deserialize;
use serde_derive::Serialize;

#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Root {
    pub data: String,
    pub key: String,
    pub nested_value: NestedValue,
    pub timestamp: i64,
}

#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct NestedValue {
    pub timestamp: String,
    pub d1: String,
    pub d2: String,
    pub time: i64,
    pub secindary_nested_value: Vec<SecindaryNestedValue>,
}

#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SecindaryNestedValue {
    pub d3: String,
    pub d4: String,
}

I would want to use the nested value later, Convert the json string to json and bind it to a similar struct.

Open to suggestions on not using a BTree and something better, but my usecase requires me to have the inner nested jsons as a string which I can bind later.

https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=4afbad3da5584bc67c9e70ae08f41cee

Upvotes: 1

Views: 1813

Answers (1)

Rainbow-Anthony Lilico
Rainbow-Anthony Lilico

Reputation: 105

(Probably not useful to OP after 2 months but giving some input for other readers.)

I strongly suspect you don't want a BTreeMap. These are primarily useful for being sorted, but these JSON key-value pairs are not something which you want to be sorted by the key. You could try a HashMap<String, String>, but again I suspect this isn't really what you want. The closest reasonable answer to your question is probably to use serde_json::Value, which is a generic type that can match any valid JSON. It is implemented as

pub enum Value {
    Null,
    Bool(bool),
    Number(Number),
    String(String),
    Array(Vec<Value>),
    Object(Map<String, Value>),
}

So this JSON would deserialise to an Object, and you can work with the contained map to do whatever you need. If you really want to put the nested value back to a string, use serde_json::to_string to convert it back at whatever point that is necessary. You can subsequently convert a Value to a more specific type like Root using serde_json::from_value.

However I will note that, in my experience, using Value is rarely the best approach. I have only used it when an API I was integrating with sent some arbitrary JSON as a part of some data, and subsequently required that same JSON to be sent back as part of another request. Your exact requirements aren't entirely clear but I will do my best to guess at what you really want:

  • To serialise (Rust to JSON, which was specified in the question but maybe not really what you meant), your structs shown should convert nicely to the JSON format specified with serde_json::to_string. Based on the rest of the question, it seems like you have some more complex requirements which may mean those need to be changed, but the principle is correct.

  • To deserialise (JSON to Rust), this depends on the data that you are getting. If you know that the data comes in the format given, you can deserialise it directly to the Root type you have defined, and the nested values will be build correctly as the appropriate types. If you need to modify the data in some way, I would strongly encourage you to do so on the deserialised types rather than on strings. Even if you want to replace some parts of it with other data which you get as a JSON string, it's better to deserialise that data (perhaps to NestedValue, or some other type) and work with those Rust types. This is far more reliable than trying to edit JSON strings. If you need to present some part of the data to an external user or service as JSON, you can serialise it again.

  • If you aren't sure that the data is going to come in the format you have written (part of your question suggests you need more generic types, perhaps because of this?), you have a couple of options based on what it may be. If some values may be missing, you can use an Option for the deserialisation type: eg. if nestedValue may be missing, use pub nested_value: Option<NestedValue> in the Root struct. If there are some other known patterns it could follow, you can use an enum to match them: eg.

#[derive(Debug, Deserialize)]
#[serde(untagged)] // This tells serde to figure out from the type structure rather than the name.
pub enum RootEnum {
    Root {
        data: String,
        key: String,
        nested_value: NestedValue, // This can be used as it was.
        timestamp: i64,
    }
    SomeOtherType {
        foo: Bar
    }
}

If you do this, you may need a match block to determine how to use the data. If you really don't know what this data may look like, you would need to use Value to deserialise it. However at that point I suspect that you wouldn't be able to use the data anyway, so you should probably attempt to deserialise to a type you know, and do something appropriate with the error you get if it doesn't match. You will need some error handling anyway since the JSON may be invalid.

In any case, this has become a long answer, hopefully it will be useful to someone.

Upvotes: 4

Related Questions