From a82ddde14d53dcb88d13094c1769026348549b18 Mon Sep 17 00:00:00 2001 From: "Heinz N. Gies" Date: Wed, 4 Oct 2023 10:11:14 +0200 Subject: [PATCH] Add skip_serializing_if Signed-off-by: Heinz N. Gies --- simd-json-derive-int/src/args.rs | 50 ++++-- simd-json-derive-int/src/deserialize.rs | 35 ++-- simd-json-derive-int/src/serialize.rs | 206 ++++++++++++++++++------ tests/skip_serializing_if.rs | 98 +++++++++++ 4 files changed, 304 insertions(+), 85 deletions(-) create mode 100644 tests/skip_serializing_if.rs diff --git a/simd-json-derive-int/src/args.rs b/simd-json-derive-int/src/args.rs index 0744ec8..a1f2643 100644 --- a/simd-json-derive-int/src/args.rs +++ b/simd-json-derive-int/src/args.rs @@ -1,24 +1,38 @@ use proc_macro2::{Ident, Literal}; use simd_json::prelude::*; -use syn::parse::{Parse, ParseStream}; +use simd_json::OwnedValue; +use syn::{ + parse::{Parse, ParseStream}, + LitStr, Path, +}; use syn::{Attribute, Field, Token}; -#[derive(Debug)] +#[derive(Debug, Default)] pub(crate) struct FieldAttrs { rename: Option, + skip_serializing_if: Option, } impl Parse for FieldAttrs { fn parse(input: ParseStream) -> syn::Result { - let mut rename = None; + let mut attrs = FieldAttrs::default(); + while !input.is_empty() { let attr: Ident = input.parse()?; match attr.to_string().as_str() { "rename" => { let _eqal_token: Token![=] = input.parse()?; - let name: Literal = input.parse()?; + let name: LitStr = input.parse()?; - rename = Some(name.to_string().trim_matches('"').to_string()); + attrs.rename = Some(name.value()); + } + "skip_serializing_if" => { + let _eqal_token: Token![=] = input.parse()?; + let function: LitStr = input.parse()?; + + let path: Path = function.parse()?; + + attrs.skip_serializing_if = Some(path); } "borrow" => (), other => { @@ -32,7 +46,7 @@ impl Parse for FieldAttrs { let _comma_token: Token![,] = input.parse()?; } } - Ok(FieldAttrs { rename }) + Ok(attrs) } } @@ -147,24 +161,30 @@ impl StructAttrs { self.deny_unknown_fields } - pub(crate) fn name(&self, field: &Field) -> Option { + pub(crate) fn skip_serializing_if(&self, field: &Field) -> Option { + get_attr(&field.attrs, "simd_json") + .or_else(|| get_attr(&field.attrs, "serde")) + .map(field_attrs) + .and_then(|a| a.skip_serializing_if) + } + pub(crate) fn name(&self, field: &Field) -> String { if let Some(attr) = get_attr(&field.attrs, "simd_json") .map(field_attrs) .and_then(|a| a.rename) { - Some(format!("{}:", simd_json::OwnedValue::from(attr).encode())) + format!("{}:", OwnedValue::from(attr).encode()) } else if let Some(attr) = get_attr(&field.attrs, "serde") .map(field_attrs) .and_then(|a| a.rename) { - Some(format!("{}:", simd_json::OwnedValue::from(attr).encode())) + format!("{}:", OwnedValue::from(attr).encode()) } else { - field.ident.as_ref().map(|ident| { - format!( - "{}:", - simd_json::OwnedValue::from(self.rename_all.apply(&ident.to_string())).encode() - ) - }) + let f = field + .ident + .as_ref() + .expect("Field is missing ident") + .to_string(); + format!("{}:", OwnedValue::from(self.rename_all.apply(&f)).encode()) } } } diff --git a/simd-json-derive-int/src/deserialize.rs b/simd-json-derive-int/src/deserialize.rs index 59693a4..3d12e00 100644 --- a/simd-json-derive-int/src/deserialize.rs +++ b/simd-json-derive-int/src/deserialize.rs @@ -23,7 +23,6 @@ fn derive_named_struct( let mut options = Vec::new(); let mut ids = Vec::new(); let mut opt_ids = Vec::new(); - let mut id: u64 = 0; let mut all_needed: u64 = 0; let deny_unknown_fields: bool = attrs.deny_unknown_fields(); let params = &generics.params; @@ -32,7 +31,7 @@ fn derive_named_struct( Some(GenericParam::Lifetime(lifetime)) => (quote! { <#params> }, quote! { #lifetime }), Some(_) => (quote! { <'input, #params> }, quote! { 'input }), }; - for f in &fields { + for (id, f) in fields.iter().enumerate() { let mut is_option = false; if let Type::Path(TypePath { path: Path { segments, .. }, @@ -44,25 +43,21 @@ fn derive_named_struct( } } - if let Some((name, ident)) = attrs - .name(f) - .and_then(|name| Some((name, f.ident.as_ref()?.clone()))) - { - let name = name.trim_matches(':').trim_matches('"').to_string(); - let bit = 1 << id; - id += 1; - if is_option { - options.push(ident.clone()); - opt_ids.push(bit); - getters.push(quote! { #ident.and_then(::std::convert::identity) }) - } else { - all_needed |= bit; - getters.push(quote! { #ident.expect(concat!("failed to get field ", #name)) }) - } - keys.push(name); - values.push(ident); - ids.push(bit); + let ident = f.ident.clone().expect("Missing ident"); + let name = attrs.name(f); + let name = name.trim_matches(':').trim_matches('"').to_string(); + let bit = 1 << id; + if is_option { + options.push(ident.clone()); + opt_ids.push(bit); + getters.push(quote! { #ident.and_then(::std::convert::identity) }) + } else { + all_needed |= bit; + getters.push(quote! { #ident.expect(concat!("failed to get field ", #name)) }) } + keys.push(name); + values.push(ident); + ids.push(bit); } let expanded = quote! { diff --git a/simd-json-derive-int/src/serialize.rs b/simd-json-derive-int/src/serialize.rs index 8d7ae71..d965b72 100644 --- a/simd-json-derive-int/src/serialize.rs +++ b/simd-json-derive-int/src/serialize.rs @@ -62,42 +62,91 @@ fn derive_named_struct( ) -> proc_macro::TokenStream { let mut keys = Vec::new(); let mut values = Vec::new(); + let mut skip_if = Vec::new(); for f in &fields { - if let Some((name, ident)) = attrs - .name(f) - .and_then(|name| Some((name, f.ident.as_ref()?.clone()))) - { - keys.push(name); - values.push(ident); - } + let ident = f.ident.clone().expect("Missing ident"); + let name = attrs.name(f); + keys.push(name); + values.push(ident); + skip_if.push(attrs.skip_serializing_if(f)); } + let expanded = if skip_if.iter().all(Option::is_none) { + if let Some((first, rest)) = keys.split_first_mut() { + *first = format!("{{{}", first); + for r in rest { + *r = format!(",{}", r); + } + }; - if let Some((first, rest)) = keys.split_first_mut() { - *first = format!("{{{}", first); - for r in rest { - *r = format!(",{}", r); + quote! { + impl #generics simd_json_derive::Serialize for #ident #generics { + #[inline] + fn json_write(&self, writer: &mut W) -> std::io::Result<()> + where + W: std::io::Write { + #( + writer.write_all(#keys.as_bytes())?; + self.#values.json_write(writer)?; + )* + writer.write_all(b"}") + } + } } - }; - - let expanded = quote! { - impl #generics simd_json_derive::Serialize for #ident #generics { - #[inline] - fn json_write(&self, writer: &mut W) -> std::io::Result<()> - where - W: std::io::Write { - #( - writer.write_all(#keys.as_bytes())?; - self.#values.json_write(writer)?; - )* - writer.write_all(b"}") + } else { + let writes = keys + .iter() + .zip(values.iter()) + .zip(skip_if.iter()) + .map(|((k, v), s)| { + if let Some(s) = s { + quote! { + if !#s(&self.#v) { + if has_written_key { + writer.write_all(b",")?; + } + has_written_key = true; + writer.write_all(#k.as_bytes())?; + self.#v.json_write(writer)?; + } + } + } else { + quote! { + if has_written_key { + writer.write_all(b",")?; + } + has_written_key = true; + writer.write_all(#k.as_bytes())?; + self.#v.json_write(writer)?; + } } + }) + .collect::>(); + quote! { + impl #generics simd_json_derive::Serialize for #ident #generics { + #[inline] + fn json_write(&self, writer: &mut W) -> std::io::Result<()> + where + W: std::io::Write { + writer.write_all(b"{")?; + let mut has_written_key = false; + #( + #writes + )* + writer.write_all(b"}") + } + } } }; TokenStream::from(expanded) } -fn derive_enum(ident: Ident, data: DataEnum, generics: Generics) -> TokenStream { +fn derive_enum( + attrs: StructAttrs, + ident: Ident, + data: DataEnum, + generics: Generics, +) -> TokenStream { let mut body_elements = Vec::new(); let variants = data.variants; let (simple, variants): (Vec<_>, Vec<_>) = @@ -222,34 +271,81 @@ fn derive_enum(ident: Ident, data: DataEnum, generics: Generics) -> TokenStream let mut named_bodies = Vec::new(); for v in named { let named_ident = &v.ident; - let fields: Vec<_> = v - .fields - .iter() - .cloned() - .map(|f| f.ident.expect("no field ident")) - .collect(); - let (first, rest) = fields.split_first().expect("zero fields"); + let mut keys = Vec::new(); + let mut values = Vec::new(); + let mut skip_if = Vec::new(); - let start = format!( - "{{{}:{{{}:", - simd_json::OwnedValue::from(v.ident.to_string()).encode(), - simd_json::OwnedValue::from(first.to_string()).encode() - ); + for f in &v.fields { + let name = attrs.name(f); + let ident = f.ident.clone().expect("Missing ident"); + keys.push(name); + values.push(ident); + skip_if.push(attrs.skip_serializing_if(f)); + } + let variant_name = simd_json::OwnedValue::from(v.ident.to_string()).encode(); - let rest_keys = rest - .iter() - .map(|f| format!(",{}:", simd_json::OwnedValue::from(f.to_string()).encode())); + named_bodies.push(if skip_if.iter().all(Option::is_none) { + let (first_key, rest_keys) = keys.split_first().expect("zero fields"); + let (first_value, rest_values) = values.split_first().expect("zero fields"); - named_bodies.push(quote! { - #ident::#named_ident{#(#fields),*} => { - writer.write_all(#start.as_bytes())?; - #first.json_write(writer)?; - #( - writer.write_all(#rest_keys.as_bytes())?; - #rest.json_write(writer)?; + let start = format!("{{{variant_name}:{{{first_key}",); + let rest_keys = rest_keys + .iter() + .map(|k| format!(",{k}")) + .collect::>(); - )* - writer.write_all(b"}}") + quote! { + #ident::#named_ident{#(#values),*} => { + writer.write_all(#start.as_bytes())?; + #first_value.json_write(writer)?; + #( + writer.write_all(#rest_keys.as_bytes())?; + #rest_values.json_write(writer)?; + + )* + writer.write_all(b"}}") + } + } + } else { + let writes = keys + .iter() + .zip(values.iter()) + .zip(skip_if.iter()) + .map(|((k, v), s)| { + if let Some(s) = s { + quote! { + + if !#s(#v) { + if has_written_key { + writer.write_all(b",")?; + } + has_written_key = true; + writer.write_all(#k.as_bytes())?; + #v.json_write(writer)?; + } + } + } else { + quote! { + if has_written_key { + writer.write_all(b",")?; + } + has_written_key = true; + writer.write_all(#k.as_bytes())?; + #v.json_write(writer)?; + } + } + }) + .collect::>(); + let prefix = format!("{{{variant_name}:{{"); + quote! { + #ident::#named_ident{#(#values),*} => { + writer.write_all(#prefix.as_bytes())?; + let mut has_written_key = false; + #( + #writes + )* + writer.write_all(b"}}") + } } }); } @@ -318,8 +414,18 @@ pub(crate) fn derive(input: TokenStream) -> TokenStream { ident, data: Data::Enum(data), generics, + attrs, .. - } => derive_enum(ident, data, generics), + } => { + let attrs = if let Some(attrs) = get_attr(&attrs, "simd_json") { + struct_attrs(attrs) + } else if let Some(attrs) = get_attr(&attrs, "serde") { + struct_attrs(attrs) + } else { + StructAttrs::default() + }; + derive_enum(attrs, ident, data, generics) + } _ => TokenStream::from(quote! {}), } } diff --git a/tests/skip_serializing_if.rs b/tests/skip_serializing_if.rs new file mode 100644 index 0000000..a8e730c --- /dev/null +++ b/tests/skip_serializing_if.rs @@ -0,0 +1,98 @@ +use simd_json_derive::Serialize; + +#[test] +fn skip_in_struct() { + #[derive(Serialize)] + struct Bla { + #[serde(skip_serializing_if = "Option::is_none")] + f1: Option, + #[serde(skip_serializing_if = "Option::is_none")] + f2: Option, + f3: Option, + } + + let b = Bla { + f1: None, + f2: None, + f3: None, + }; + let s = b.json_string().unwrap(); + assert_eq!(r#"{"f3":null}"#, s); + + let b = Bla { + f1: Some(1), + f2: None, + f3: None, + }; + let s = b.json_string().unwrap(); + assert_eq!(r#"{"f1":1,"f3":null}"#, s); + + let b = Bla { + f1: None, + f2: Some(2), + f3: None, + }; + let s = b.json_string().unwrap(); + assert_eq!(r#"{"f2":2,"f3":null}"#, s); + + let b = Bla { + f1: Some(1), + f2: None, + f3: Some(3), + }; + let s = b.json_string().unwrap(); + assert_eq!(r#"{"f1":1,"f3":3}"#, s); +} + +#[test] +fn skip_in_enum() { + #[derive(Serialize)] + enum Bla { + Blubb { + #[serde(skip_serializing_if = "Option::is_none")] + f1: Option, + #[serde(skip_serializing_if = "Option::is_none")] + f2: Option, + f3: Option, + }, + Blargh { + #[serde(skip_serializing_if = "Option::is_none")] + f1: Option, + #[serde(skip_serializing_if = "Option::is_none")] + f2: Option, + f3: Option, + }, + } + + let b = Bla::Blubb { + f1: None, + f2: None, + f3: None, + }; + let s = b.json_string().unwrap(); + assert_eq!(r#"{"Blubb":{"f3":null}}"#, s); + + let b = Bla::Blubb { + f1: Some(1), + f2: None, + f3: None, + }; + let s = b.json_string().unwrap(); + assert_eq!(r#"{"Blubb":{"f1":1,"f3":null}}"#, s); + + let b = Bla::Blargh { + f1: None, + f2: Some(2), + f3: None, + }; + let s = b.json_string().unwrap(); + assert_eq!(r#"{"Blargh":{"f2":2,"f3":null}}"#, s); + + let b = Bla::Blargh { + f1: Some(1), + f2: None, + f3: Some(3), + }; + let s = b.json_string().unwrap(); + assert_eq!(r#"{"Blargh":{"f1":1,"f3":3}}"#, s); +}