mongodb / bson-rust

Encoding and decoding support for BSON in Rust
MIT License
406 stars 134 forks source link

RUST-677 Serialization formatting options #230

Open AlexKovalevych opened 3 years ago

AlexKovalevych commented 3 years ago

After retrieving data from database like:

use mongodb::bson::from_document;

let data = from_document::<Value>(bson_document)?;

I'm not able to serialize data to json with custom datetime format getting datetime fields like:

...
        "updated_at": {
          "$date": "2021-02-01T14:50:01.504Z"
        },
...

I can't use serde helpers, since i don't know the data schema. The only solution i see right now is to manually iterate all fields in bson_document recursively and in case of Bson::DateTime type convert it to Bson::String type with desired datetime format. Is there a better solution?

patrickfreed commented 3 years ago

Hi @AlexKovalevych!

So right now, the only format we support for deserializing arbitrary BSON to an arbitrary serialization format is Extended JSON, which is what you're seeing there with the $date. We think allowing configuration in this area would be a nice addition to our API, so I've filed RUST-677 to track the work for that.

In the meantime, you could define a wrapper struct and implement a custom Serialize implementation on it, which could possibly make this a little more straightforward.

Here's a pretty inefficient example using a lot of clones:

struct MyWrapper {                                                                                         
    wrapped: Bson                                                                                          
}                                                                                                          

impl Serialize for MyWrapper {                                                                             
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>                                       
    where                                                                                                  
        S: serde::Serializer                                                                               
    {                                                                                                      
        match self.wrapped {                                                                               
            Bson::DateTime(ref d) => {                                                                     
                d.serialize(serializer)                                                                    
            }                                                                                              
            Bson::Document(ref d) => {                                                                     
                let map: HashMap<String, MyWrapper> = d.iter().map(|(k, v)| {                              
                    (k.clone(), MyWrapper { wrapped: v.clone() })                                          
                }).collect();                                                                              
                map.serialize(serializer)                                                                  
            },                                                                                             
            Bson::Array(ref a) => {                                                                        
                let vec: Vec<MyWrapper> = a.iter().map(|b| { MyWrapper { wrapped: b.clone() } }).collect();
                vec.serialize(serializer)                                                                  
            }                                                                                              
            ref a @ _ => a.serialize(serializer)                                                           
        }                                                                                                  
    }                                                                                                      
}                                                                                                          

And then you'd use serde_json's serialization functionality:

let doc = doc! { "date": Bson::DateTime(Utc::now()) };                               
let json = serde_json::to_value(MyWrapper { wrapped: Bson::Document(doc) }).unwrap();
println!("{}", json);  // {"date":"2021-02-20T00:36:46.597117805Z"}                                                              

This could be optimized to get rid of the clones, but this was just the simplest way I could think of implementing it for demonstration purposes.

AlexKovalevych commented 3 years ago

Got it, thank you for detailed response