diff --git a/README.md b/README.md index 9214a77..d00b046 100644 --- a/README.md +++ b/README.md @@ -45,6 +45,10 @@ For more examples take a look at [tests](/tests) | field: `Option` | yes | yes | yes | no | | field: `i*`/`f*`/`String`/`T: De*/Ser*` | yes | yes | yes | no | | field attribute: `#[nserde(default)]` | yes | no | yes | no | +| field attribute: `#[nserde(deserialize_json_with = "")]` | yes | no | no | no | +| field attribute: `#[nserde(deserialize_json_with = "")]` | yes | no | no | no | +| field attribute: `#[nserde(deserialize_bin_with = "")]` | no | yes | no | no | +| field attribute: `#[nserde(deserialize_bin_with = "")]` | no | yes | no | no | | field attribute: `#[nserde(rename = "")]` | yes | yes | yes | no | | field attribute: `#[nserde(proxy = "")]` | no | yes | no | no | | field attribute: `#[nserde(serialize_none_as_null)]` | yes | no | no | no | diff --git a/derive/src/serde_bin.rs b/derive/src/serde_bin.rs index 32ce0b5..09feab3 100644 --- a/derive/src/serde_bin.rs +++ b/derive/src/serde_bin.rs @@ -41,6 +41,9 @@ pub fn derive_ser_bin_struct(struct_: &Struct) -> TokenStream { let (generic_w_bounds, generic_no_bounds) = struct_bounds_strings(struct_, "SerBin"); for field in &struct_.fields { + let field_name: &String = field.field_name.as_ref().unwrap(); + let field_serializer = crate::shared::attrs_serialize_bin_with(&field.attributes); + if let Some(proxy) = crate::shared::attrs_proxy(&field.attributes) { l!( body, @@ -48,12 +51,20 @@ pub fn derive_ser_bin_struct(struct_: &Struct) -> TokenStream { proxy, field.field_name.as_ref().unwrap() ); - l!(body, "proxy.ser_bin(s);"); + l!( + body, + &(field_serializer + .map(|serializer: String| format!("{}(&proxy, s);", serializer)) + .as_ref() + .map(|s| s.as_str()) + .unwrap_or_else(|| "proxy.ser_bin(s);")) + ) } else { l!( body, - "self.{}.ser_bin(s);", - field.field_name.as_ref().unwrap() + &field_serializer + .map(|serializer: String| format!("{}(&self.{}, s);", serializer, field_name)) + .unwrap_or_else(|| format!("self.{}.ser_bin(s);", field_name)) ); } } @@ -80,11 +91,25 @@ pub fn derive_ser_bin_struct_unnamed(struct_: &Struct) -> TokenStream { let (generic_w_bounds, generic_no_bounds) = struct_bounds_strings(struct_, "SerBin"); for (n, field) in struct_.fields.iter().enumerate() { + let field_serializer = crate::shared::attrs_serialize_bin_with(&field.attributes); + if let Some(proxy) = crate::shared::attrs_proxy(&field.attributes) { l!(body, "let proxy: {} = Into::into(&self.{});", proxy, n); - l!(body, "proxy.ser_bin(s);"); + l!( + body, + &(field_serializer + .map(|serializer: String| format!("{}(&proxy, s);", serializer)) + .as_ref() + .map(|s| s.as_str()) + .unwrap_or_else(|| "proxy.ser_bin(s);")) + ); } else { - l!(body, "self.{}.ser_bin(s);", n); + l!( + body, + &field_serializer + .map(|serializer: String| format!("{}(&self.{}, s);", serializer, n)) + .unwrap_or_else(|| format!("self.{}.ser_bin(s);", n)) + ); } } format!( @@ -110,16 +135,24 @@ pub fn derive_de_bin_struct(struct_: &Struct) -> TokenStream { let (generic_w_bounds, generic_no_bounds) = struct_bounds_strings(struct_, "DeBin"); for field in &struct_.fields { + let de_bin_with = shared::attrs_deserialize_bin_with(&field.attributes) + .map(|with| format!("{}(o, d)", with)); + let ref de_bin_expr: &str = de_bin_with + .as_ref() + .map(|w| w.as_str()) + .unwrap_or("DeBin::de_bin(o, d)"); + if let Some(proxy) = crate::shared::attrs_proxy(&field.attributes) { l!(body, "{}: {{", field.field_name.as_ref().unwrap()); - l!(body, "let proxy: {} = DeBin::de_bin(o, d)?;", proxy); + l!(body, "let proxy: {} = {}?;", proxy, de_bin_expr); l!(body, "Into::into(&proxy)"); l!(body, "},") } else { l!( body, - "{}: DeBin::de_bin(o, d)?,", - field.field_name.as_ref().unwrap() + "{}: {}?,", + field.field_name.as_ref().unwrap(), + de_bin_expr ); } } @@ -149,13 +182,20 @@ pub fn derive_de_bin_struct_unnamed(struct_: &Struct) -> TokenStream { let (generic_w_bounds, generic_no_bounds) = struct_bounds_strings(struct_, "DeBin"); for (n, field) in struct_.fields.iter().enumerate() { + let de_bin_with = shared::attrs_deserialize_bin_with(&field.attributes) + .map(|with| format!("{}(o, d)", with)); + let ref de_bin_expr: &str = de_bin_with + .as_ref() + .map(|w| w.as_str()) + .unwrap_or("DeBin::de_bin(o, d)"); + if let Some(proxy) = crate::shared::attrs_proxy(&field.attributes) { l!(body, "{}: {{", n); - l!(body, "let proxy: {} = DeBin::de_bin(o, d)?;", proxy); + l!(body, "let proxy: {} = {}?;", proxy, de_bin_expr); l!(body, "Into::into(&proxy)"); l!(body, "},") } else { - l!(body, "{}: DeBin::de_bin(o, d)?,", n); + l!(body, "{}: {}?,", n, de_bin_expr); } } diff --git a/derive/src/serde_json.rs b/derive/src/serde_json.rs index ba2ad83..563ee0c 100644 --- a/derive/src/serde_json.rs +++ b/derive/src/serde_json.rs @@ -47,6 +47,8 @@ pub fn derive_ser_json_struct(struct_: &Struct) -> TokenStream { if struct_.fields.len() >= 1 { for (_index, field) in struct_.fields.iter().enumerate() { + let serialize_json_with = shared::attrs_serialize_json_with(&field.attributes); + let struct_fieldname = field.field_name.clone().unwrap(); let json_fieldname = shared::attrs_rename(&field.attributes).unwrap_or_else(|| struct_fieldname.clone()); @@ -60,12 +62,16 @@ pub fn derive_ser_json_struct(struct_: &Struct) -> TokenStream { let proxy_attr = crate::shared::attrs_proxy(&field.attributes); let struct_null_on_none = shared::attrs_serialize_none_as_null(&struct_.attributes); let field_null_on_none = shared::attrs_serialize_none_as_null(&field.attributes); - let null_on_none = (field_null_on_none || struct_null_on_none) && proxy_attr.is_none(); - let field_header = &format!("if first_field_was_serialized {{ + let null_on_none = + (field_null_on_none || struct_null_on_none) && proxy_attr.is_none(); + let field_header = &format!( + "if first_field_was_serialized {{ s.conl(); }}; first_field_was_serialized = true; - s.field(d+1, \"{}\");", json_fieldname); + s.field(d+1, \"{}\");", + json_fieldname + ); l!( s, "{} @@ -92,9 +98,13 @@ pub fn derive_ser_json_struct(struct_: &Struct) -> TokenStream { }}; first_field_was_serialized = true; s.field(d+1,\"{}\"); - {}.ser_json(d+1, s);", + {}", json_fieldname, - proxied_field + if let Some(custom_serializer) = serialize_json_with { + format!("{}(&{}, d+1, s);", custom_serializer, proxied_field) + } else { + format!("{}.ser_json(d+1, s);", proxied_field) + } ); } } @@ -126,6 +136,7 @@ pub fn derive_de_json_named(name: &str, defaults: bool, fields: &[Field]) -> Tok let mut local_vars = Vec::new(); let mut struct_field_names = Vec::new(); let mut json_field_names = Vec::new(); + // Vec of (json_field_name, local_var_name, custom_initializer) let mut matches = Vec::new(); let mut unwraps = Vec::new(); @@ -136,6 +147,8 @@ pub fn derive_de_json_named(name: &str, defaults: bool, fields: &[Field]) -> Tok let localvar = format!("_{}", struct_fieldname); let field_attr_default = shared::attrs_default(&field.attributes); let field_attr_default_with = shared::attrs_default_with(&field.attributes); + let deserialize_json_with = shared::attrs_deserialize_json_with(&field.attributes); + let default_val = if let Some(v) = field_attr_default { if let Some(mut val) = v { if field.ty.base() == "String" @@ -198,7 +211,11 @@ pub fn derive_de_json_named(name: &str, defaults: bool, fields: &[Field]) -> Tok localvar, proxified_t, struct_fieldname )); } - matches.push((json_fieldname.clone(), localvar.clone())); + matches.push(( + json_fieldname.clone(), + localvar.clone(), + deserialize_json_with, + )); local_vars.push(localvar); } else { unwraps.push(default_val.unwrap_or_else(|| String::from("Default::default()"))); @@ -217,12 +234,16 @@ pub fn derive_de_json_named(name: &str, defaults: bool, fields: &[Field]) -> Tok if json_field_names.len() != 0 { l!(r, "match AsRef::::as_ref(&s.strbuf) {"); - for (json_field_name, local_var) in matches.iter() { + for (json_field_name, local_var, deserialize_with) in matches.iter() { l!( r, - "\"{}\" => {{s.next_colon(i) ?;{} = Some(DeJson::de_json(s, i) ?)}},", + "\"{}\" => {{s.next_colon(i) ?;{} = Some({} ?)}},", json_field_name, - local_var + local_var, + deserialize_with + .as_ref() + .map(|deserializer_name| format!("{}(s, i)", deserializer_name)) + .unwrap_or_else(|| "DeJson::de_json(s, i)".to_string()) ); } // TODO: maybe introduce "exhaustive" attribute? @@ -591,8 +612,14 @@ pub fn derive_de_json_struct_unnamed(struct_: &Struct) -> TokenStream { let transparent = shared::attrs_transparent(&struct_.attributes); - for _ in &struct_.fields { - l!(body, "{ let r = DeJson::de_json(s, i)?;"); + for field in &struct_.fields { + let deserialize_json_with = shared::attrs_deserialize_json_with(&field.attributes); + if let Some(deserialize_json_with) = deserialize_json_with { + l!(body, "{{ let r = {}(s, i)?;", deserialize_json_with); + } else { + l!(body, "{ let r = DeJson::de_json(s, i)?;"); + } + if struct_.fields.len() != 1 { l!(body, " s.eat_comma_block(i)?;"); } diff --git a/derive/src/shared.rs b/derive/src/shared.rs index 62d9449..d35b1be 100644 --- a/derive/src/shared.rs +++ b/derive/src/shared.rs @@ -62,6 +62,50 @@ pub fn attrs_default_with(attributes: &[crate::parse::Attribute]) -> Option Option { + attributes.iter().find_map(|attr| { + if attr.tokens.len() == 2 && attr.tokens[0] == "deserialize_json_with" { + Some(attr.tokens[1].clone()) + } else { + None + } + }) +} + +#[cfg(any(feature = "json"))] +pub fn attrs_serialize_json_with(attributes: &[crate::parse::Attribute]) -> Option { + attributes.iter().find_map(|attr| { + if attr.tokens.len() == 2 && attr.tokens[0] == "serialize_json_with" { + Some(attr.tokens[1].clone()) + } else { + None + } + }) +} + +#[cfg(any(feature = "binary"))] +pub fn attrs_deserialize_bin_with(attributes: &[crate::parse::Attribute]) -> Option { + attributes.iter().find_map(|attr| { + if attr.tokens.len() == 2 && attr.tokens[0] == "deserialize_bin_with" { + Some(attr.tokens[1].clone()) + } else { + None + } + }) +} + +#[cfg(any(feature = "binary"))] +pub fn attrs_serialize_bin_with(attributes: &[crate::parse::Attribute]) -> Option { + attributes.iter().find_map(|attr| { + if attr.tokens.len() == 2 && attr.tokens[0] == "serialize_bin_with" { + Some(attr.tokens[1].clone()) + } else { + None + } + }) +} + #[cfg(feature = "json")] pub fn attrs_transparent(attributes: &[crate::parse::Attribute]) -> bool { attributes diff --git a/tests/bin.rs b/tests/bin.rs index 3a3371d..6f298ea 100644 --- a/tests/bin.rs +++ b/tests/bin.rs @@ -6,7 +6,7 @@ use std::{array, sync::atomic::AtomicBool}; use alloc::collections::{BTreeMap, BTreeSet, LinkedList}; -use nanoserde::{DeBin, SerBin}; +use nanoserde::{DeBin, DeBinErr, SerBin}; #[test] fn binary() { @@ -139,6 +139,72 @@ fn field_ignore_self_bound() { assert_eq!(foo_base, deser.foo); } +#[test] +fn field_custom_serialize() { + fn custom_serializer(x: &i32, output: &mut Vec) { + let mut as_bytes = Vec::new(); + as_bytes.append(&mut [99, 99, 99, 99].to_vec()); + as_bytes.append(&mut x.to_le_bytes().to_vec()); + as_bytes.append(&mut [44, 44, 44, 44].to_vec()); + output.append(&mut as_bytes) + } + + fn custom_deserializer(offset: &mut usize, input: &[u8]) -> Result { + if input.len() < *offset + 12 { + // bounds check + return Err(DeBinErr { + o: *offset, + l: 0, + s: 0, + }); + } + + // Check that the custom header and footer are present and match the expected values + let header = input[*offset..*offset + 4].as_ref(); + let footer = input[*offset + 8..*offset + 12].as_ref(); + if header.iter().any(|&x| x != 99) || footer.iter().any(|&x| x != 44) { + return Err(DeBinErr { + o: *offset, + l: 0, + s: 0, + }); + } + + *offset += 12; + let content = [ + input[*offset + 4], + input[*offset + 5], + input[*offset + 6], + input[*offset + 7], + ]; + Ok(i32::from_le_bytes(content)) + } + + #[derive(DeBin, SerBin, PartialEq, Debug)] + pub struct Test { + #[nserde( + serialize_bin_with = "custom_serializer", + deserialize_bin_with = "custom_deserializer" + )] + x: i32, + y: i32, + } + let test = Test { x: 1, y: 2 }; + + let bytes = SerBin::serialize_bin(&test); + assert_eq!( + bytes, + vec![ + 99, 99, 99, 99, // x - custom header + 1, 0, 0, 0, // x - content of field + 44, 44, 44, 44, // x - custom footer + 2, 0, 0, 0 // y - default serialization + ] + ); + let test_deserialized = DeBin::deserialize_bin(&bytes).unwrap(); + assert_eq!(test, test_deserialized); +} + #[test] fn struct_proxy() { #[derive(PartialEq, Debug)] diff --git a/tests/json.rs b/tests/json.rs index 0f11100..4ead107 100644 --- a/tests/json.rs +++ b/tests/json.rs @@ -326,6 +326,53 @@ fn de_field_default() { assert_eq!(test.foo2.x, 3); } +#[test] +fn de_field_deserialize_json_with() { + use nanoserde::DeJsonState; + fn custom_deserializer( + s: &mut DeJsonState, + i: &mut std::str::Chars, + ) -> Result { + let c: String = DeJson::de_json(s, i)?; + Ok(c + "--custom-deserializer") + } + + #[derive(DeJson)] + pub struct Test { + #[nserde(deserialize_json_with = "custom_deserializer")] + a: String, + } + + let json = r#"{ + "a": "input", + }"#; + + let test: Test = DeJson::deserialize_json(json).unwrap(); + assert_eq!(test.a, "input--custom-deserializer"); +} + +#[test] +fn de_field_serialize_json_with() { + use nanoserde::SerJsonState; + fn custom_serializer(value: &i32, _: usize, s: &mut SerJsonState) { + s.out.push_str(&format!("\"{}--custom-serializer\"", value)); + } + + #[derive(SerJson)] + struct Test { + #[nserde(serialize_json_with = "custom_serializer")] + a: i32, + b: i32, + } + + let value = Test { a: 1, b: 2 }; + + assert_eq!( + SerJson::serialize_json(&value), + r#"{"a":"1--custom-serializer","b":2}"# + ); +} + #[test] fn ser_none_as_null() { #[derive(SerJson)]