Serde question: Access to a shared context data within serialize and deserialize

I have another serde question. Is there a mechanism within serde to allow some kind of custom context data to be made available to serializers and deserializers through the serde state? Preferably mutable, in the case of deserializers.

The below code shows what I'm trying to do, but it uses hideous things like mutable statics and unsafe. There's gotta be a better way...

Is there a sanctioned way to embed a custom type into a Serializer?

I found this thread advising a parallel set of structs. This would work and it's definitely better than the static / unsafe approach. But it's a lot of typing.

Thank you all for the nudges in the right direction.

extern crate serde;
extern crate serde_json;
use crate::serde_json::json;

pub struct Context {
    name_table : Option<Vec<String>>
}

struct IndexType(usize);

impl serde::ser::Serialize for IndexType {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where S: serde::ser::Serializer {

        unsafe {
            if let Some(name_table) = &MY_CONTEXT.name_table {
                serializer.serialize_str(name_table[self.0].as_str())
            } else {
                serializer.serialize_u64(self.0 as u64)
            }
        } //end unsafe
    }
}

static mut MY_CONTEXT : Context = Context{name_table : None};

fn main() {

    //Init the context
    unsafe {
        MY_CONTEXT.name_table = Some(vec!["zero".to_string(), "one".to_string()]);
    } // end unsafe

    let my_index = IndexType(1);

    let my_json = json!(my_index);
    println!("{}", serde_json::to_string_pretty(&my_json).unwrap());
}

You could make a new type, and implement Serialize for that type.

struct WithContext<'a> {
    pub context: &'a Context,
    pub index: IndexType,
}

impl Serialize for WithContext<'_> {
    // ...
}

This is the safe way to do it. You could also use once_cell or lazy_static to safely make the context global, but that's harder to test.

Thank you for the reply. The parallel "WithContext" struct was essentially what the other thread created by @ilammy was saying.

Perhaps I need to level-up my macro writing chops so I can write #derive[(SerDeWithContext)] or something once, and then derive it for all my structs that follow this pattern.

Also I still haven't wrapped my head around how the parallel struct "WithContext" approach works for deserialize. Does it need to work in two passes, first deserializing the input into a hierarchy of objects that track the structure of the input stream, and then translating that structure of objects into a the ultimate form?

Thanks again for the reply.

Why don't you just serialize this enum? You can also easily deserialize this enum.

#[derive(Serialize, Deserialize)]
enum Foo {
    str(&'a str),
    u64(u64),
}

I'm not convinced this would actually compile. The string slice case is problematic: How does one deserialize to a borrow? Where is the owner? And where is the data stored?

It will be a slice into the data you parsed. See relevant post of mine here. This is why the Deserialize trait has a 'de lifetime.

2 Likes

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.