I have written some rust code but mostly for internal use in my company. Of course I use serde a lot. I use it mainly for providing JSON output. On the one hand I have a CLI which provides JSON output for a better parsing experience (when called via another program) and on the other hand I provide a WASM package for use in our server. Both of these "api frontends" use some libraries of mine, which I created to share functionality betweent the CLI and the separate WASM lib.
I researched how other people use serde und read some code on crates.io. The common practice seems to be to gate serde behind a "serde" feature which makes sense for me. I just cannot wrap my head around how to properly treat renaming to camel case. I know about the rename_all attribute and stuff, but is it good practice to provide separate feature flags for separate naming conventions or how would one typically handle a case where you want to provide serde, but do not know in which naming convention it shall be used?
I have the feeling I am missing a piece in a puzzle. Could you be so kind to give me some hints to libraries which provide idiomatic serde support or give me some general tips?
Making naming convention configurable at compile time is a bit of a footgun for your downstream users. Imagine if one program used one version of your crate with the pascal-case-serde feature enabled and another used the kebab-case-serde feature. They wouldn't be able to talk with each other and it'd be a disaster.
Try to keep it simple and use a single naming convention across your entire codebase (I think kebab-case is easier to read, but that's just personal preference).
Thanks for your response!
I get your point. The thing is.. how would I then include this User struct in e.g. a WASM library. Currently, I am utilizing wasm_bindgen::JsValue as return value for my API functions. There, the consumer will most likely expect the serialized content to be in camel case.
My current understanding is, that I would need to create a newtype around User to provide my own serde implementation with camel case naming.
I think you're trying to do something that shouldn't be done, because it's intrinsically fragile. Consider these two serde use cases:
The serialization schema is already defined (e.g. by an API server that already exists); you write data structures to teach serde to understand that schema. You use the keys of that schema, so you pick whichever rename_all option fits that schema.
You're defining a new schema for storing data structures your library creates. In that case, you choosing to use rename_all or not is part of defining the schema.
(By “schema” I just mean the possible set of serialized values, not necessarily writing it down in a schema document that actually exists.)
Neither of these is what you seem to be doing; instead, you are proposing to define library types whose serialization fits well enough a not-yet-defined schema. But this is impossible in the general case, and serde doesn't provide tools to do that even in simple cases.
Instead, the downstream crate which is using your library can use #[serde(with)] or remote derive to fully define its own serialization schema, while converting from/to your library types.
Yes I think I confused two separate concepts.
Crates like hex or bitflags provide a serde feature to enable the conversion to a "string literal" e.g. for bitflags "A | B".
So therefore, I can use these "fundamental" types as literals in my own types.
use serde::Serialize;
bitflags::bitflags! {
#[derive(Serialize, Debug)]
#[serde(transparent)]
pub struct Flags: u32 {
const A = 1;
const B = 2;
const C = 4;
const D = 8;
}
}
#[derive(Serialize)]
struct MyBits {
flags: Flags,
}
fn main() {
let my_bits = MyBits {
flags: Flags::A | Flags::B,
};
let serialized = serde_json::to_string(&my_bits).unwrap();
println!("{}", serialized);
}
Which gives me: {"flags":"A | B"}
This conversion makes sense and is not linked to a "schema".
However, I confused this usage of serde with a somehow API related usage which of course is always linked to a "schema" and therefore can never be right for every case.
I think I get the difference know, if I understood your response correctly. Thank you for your response!
I'd say that it is: bitflags decided that the representation should have | separators in it. That's a small piece of schema. If that schema suits your use case, you can use it; if it doesn't suit, you have to write your own impl Serialize to match your own desired schema.