Skip to content

Commit

Permalink
Add support for serializing hex strings without quotes
Browse files Browse the repository at this point in the history
  • Loading branch information
aljen committed Dec 11, 2024
1 parent 7466647 commit d92ec0c
Show file tree
Hide file tree
Showing 2 changed files with 77 additions and 1 deletion.
45 changes: 44 additions & 1 deletion src/ser/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,8 @@ pub struct PrettyConfig {
pub compact_maps: bool,
/// Enable explicit number type suffixes like `1u16`
pub number_suffixes: bool,
/// Whether to serialize hex strings (0x...) without quotes
pub hex_as_raw: bool,
/// Additional path-based field metadata to serialize
pub path_meta: Option<path_meta::Field>,
}
Expand Down Expand Up @@ -341,6 +343,27 @@ impl PrettyConfig {

self
}

/// Configures whether hex strings should be serialized without quotes.
///
/// When `true`, the hex string `0x1234` will serialize to
/// ```ignore
/// 0x1234
/// # ;
/// ```
/// When `false`, the hex string `0x1234` will serialize to
/// ```ignore
/// "0x1234"
/// # ;
/// ```
///
/// Default: `true`
#[must_use]
pub fn hex_as_raw(mut self, hex_as_raw: bool) -> Self {
self.hex_as_raw = hex_as_raw;

self
}
}

impl Default for PrettyConfig {
Expand All @@ -363,6 +386,7 @@ impl Default for PrettyConfig {
compact_structs: false,
compact_maps: false,
number_suffixes: false,
hex_as_raw: true,
path_meta: None,
}
}
Expand All @@ -383,6 +407,15 @@ pub struct Serializer<W: fmt::Write> {
implicit_some_depth: usize,
}

/// Returns true if the string is a valid hexadecimal number starting with 0x/0X
#[inline]
fn is_valid_hex(s: &str) -> bool {
if !s.starts_with("0x") && !s.starts_with("0X") {
return false;
}
s[2..].chars().all(|c| c.is_ascii_hexdigit())
}

fn indent<W: fmt::Write>(output: &mut W, config: &PrettyConfig, pretty: &Pretty) -> fmt::Result {
if pretty.indent <= config.depth_limit {
for _ in 0..pretty.indent {
Expand Down Expand Up @@ -489,6 +522,12 @@ impl<W: fmt::Write> Serializer<W> {
.map_or(true, |(ref config, _)| config.escape_strings)
}

fn hex_as_raw(&self) -> bool {
self.pretty
.as_ref()
.map_or(true, |(ref config, _)| config.hex_as_raw)
}

fn start_indent(&mut self) -> Result<()> {
if let Some((ref config, ref mut pretty)) = self.pretty {
pretty.indent += 1;
Expand Down Expand Up @@ -777,7 +816,11 @@ impl<'a, W: fmt::Write> ser::Serializer for &'a mut Serializer<W> {

fn serialize_str(self, v: &str) -> Result<()> {
if self.escape_strings() {
self.serialize_escaped_str(v)?;
if self.hex_as_raw() && is_valid_hex(v) {
self.output.write_str(v)?;
} else {
self.serialize_escaped_str(v)?;
}
} else {
self.serialize_unescaped_or_raw_str(v)?;
}
Expand Down
33 changes: 33 additions & 0 deletions tests/numbers.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use ron::{
de::from_str,
error::{Error, Position, SpannedError},
ser::{to_string_pretty, PrettyConfig},
};

#[test]
Expand Down Expand Up @@ -111,3 +112,35 @@ fn test_dec() {
})
);
}

#[test]
fn test_hex_serialization() {
let config = PrettyConfig::new()
.hex_as_raw(true);

// Test valid hex without quotes
let val = "0x1234";
let serialized = to_string_pretty(&val, config.clone()).unwrap();
assert_eq!(serialized, "0x1234");

// Test uppercase hex
let val = "0X1A5B";
let serialized = to_string_pretty(&val, config.clone()).unwrap();
assert_eq!(serialized, "0X1A5B");

// Test that normal strings are still escaped
let val = "normal string";
let serialized = to_string_pretty(&val, config.clone()).unwrap();
assert_eq!(serialized, "\"normal string\"");

// Test that invalid hex is treated as a normal string
let val = "0xGGG";
let serialized = to_string_pretty(&val, config.clone()).unwrap();
assert_eq!(serialized, "\"0xGGG\"");

// Test with hex_as_raw disabled
let config = PrettyConfig::new().hex_as_raw(false);
let val = "0x1234";
let serialized = to_string_pretty(&val, config).unwrap();
assert_eq!(serialized, "\"0x1234\"");
}

0 comments on commit d92ec0c

Please sign in to comment.