Skip to content

Commit eeb73ed

Browse files
lukepuplettclaude
andcommitted
Add native JSON serialization support for Hex type
- Add HexJsonConverter and NullableHexJsonConverter for automatic serialization - Add JsonSerializerOptionsExtensions.ConfigureForHex() for easy setup - Add comprehensive test coverage with 36 new tests - Enable seamless Hex usage in storage DTOs without manual conversion - Consumers can now use Hex properties naturally in JSON APIs 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]>
1 parent d788060 commit eeb73ed

File tree

4 files changed

+962
-0
lines changed

4 files changed

+962
-0
lines changed
Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
using System;
2+
using System.Text.Json;
3+
using System.Text.Json.Serialization;
4+
5+
namespace Evoq.Blockchain;
6+
7+
/// <summary>
8+
/// JSON converter for the <see cref="Hex"/> type that enables automatic serialization and deserialization.
9+
/// </summary>
10+
/// <remarks>
11+
/// <para>
12+
/// This converter enables <see cref="Hex"/> values to be seamlessly serialized to and from JSON as hex strings.
13+
/// During serialization, <see cref="Hex"/> values are converted to their string representation (e.g., "0x1234abcd").
14+
/// During deserialization, hex strings are parsed back into <see cref="Hex"/> values.
15+
/// </para>
16+
///
17+
/// <para>
18+
/// <strong>Usage:</strong> Register this converter with your <see cref="JsonSerializerOptions"/>:
19+
/// </para>
20+
///
21+
/// <code>
22+
/// var options = new JsonSerializerOptions
23+
/// {
24+
/// Converters = { new HexJsonConverter() }
25+
/// };
26+
///
27+
/// // Now Hex properties in your DTOs will be automatically serialized/deserialized
28+
/// string json = JsonSerializer.Serialize(myObject, options);
29+
/// var result = JsonSerializer.Deserialize&lt;MyType&gt;(json, options);
30+
/// </code>
31+
///
32+
/// <para>
33+
/// <strong>Supported Formats:</strong> The converter accepts hex strings with or without the "0x" prefix during
34+
/// deserialization, but always serializes with the "0x" prefix for consistency.
35+
/// </para>
36+
///
37+
/// <para>
38+
/// <strong>Error Handling:</strong> Invalid hex strings during deserialization will throw a <see cref="JsonException"/>
39+
/// with a descriptive error message. This includes strings with invalid hex characters or odd-length hex strings
40+
/// (unless using lenient parsing options).
41+
/// </para>
42+
/// </remarks>
43+
public class HexJsonConverter : JsonConverter<Hex>
44+
{
45+
/// <summary>
46+
/// Reads a JSON token and converts it to a <see cref="Hex"/> value.
47+
/// </summary>
48+
/// <param name="reader">The JSON reader to read from.</param>
49+
/// <param name="typeToConvert">The type being converted (should be <see cref="Hex"/>).</param>
50+
/// <param name="options">The JSON serializer options.</param>
51+
/// <returns>A <see cref="Hex"/> value parsed from the JSON string.</returns>
52+
/// <exception cref="JsonException">Thrown when the JSON token is not a string or when the hex string is invalid.</exception>
53+
public override Hex Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
54+
{
55+
if (reader.TokenType != JsonTokenType.String)
56+
{
57+
throw new JsonException($"Expected string token for Hex deserialization, but got {reader.TokenType}");
58+
}
59+
60+
string? hexString = reader.GetString();
61+
62+
if (hexString == null)
63+
{
64+
throw new JsonException("Cannot deserialize null string to Hex");
65+
}
66+
67+
try
68+
{
69+
return Hex.Parse(hexString);
70+
}
71+
catch (ArgumentException ex)
72+
{
73+
throw new JsonException($"Invalid hex string '{hexString}': {ex.Message}", ex);
74+
}
75+
catch (FormatException ex)
76+
{
77+
throw new JsonException($"Invalid hex string format '{hexString}': {ex.Message}", ex);
78+
}
79+
}
80+
81+
/// <summary>
82+
/// Writes a <see cref="Hex"/> value to JSON as a hex string.
83+
/// </summary>
84+
/// <param name="writer">The JSON writer to write to.</param>
85+
/// <param name="value">The <see cref="Hex"/> value to serialize.</param>
86+
/// <param name="options">The JSON serializer options.</param>
87+
public override void Write(Utf8JsonWriter writer, Hex value, JsonSerializerOptions options)
88+
{
89+
writer.WriteStringValue(value.ToString());
90+
}
91+
}
92+
93+
/// <summary>
94+
/// JSON converter for nullable <see cref="Hex"/> types that enables automatic serialization and deserialization.
95+
/// </summary>
96+
/// <remarks>
97+
/// <para>
98+
/// This converter handles nullable <see cref="Hex"/> properties in DTOs, serializing null values as JSON null
99+
/// and deserializing JSON null back to null <see cref="Hex"/> values.
100+
/// </para>
101+
///
102+
/// <para>
103+
/// <strong>Usage:</strong> This converter is typically registered alongside <see cref="HexJsonConverter"/>:
104+
/// </para>
105+
///
106+
/// <code>
107+
/// var options = new JsonSerializerOptions
108+
/// {
109+
/// Converters = {
110+
/// new HexJsonConverter(),
111+
/// new NullableHexJsonConverter()
112+
/// }
113+
/// };
114+
/// </code>
115+
///
116+
/// <para>
117+
/// <strong>Note:</strong> In most cases, you only need to register <see cref="HexJsonConverter"/> as System.Text.Json
118+
/// will automatically handle nullable conversions. This converter is provided for explicit nullable handling scenarios.
119+
/// </para>
120+
/// </remarks>
121+
public class NullableHexJsonConverter : JsonConverter<Hex?>
122+
{
123+
/// <summary>
124+
/// Reads a JSON token and converts it to a nullable <see cref="Hex"/> value.
125+
/// </summary>
126+
/// <param name="reader">The JSON reader to read from.</param>
127+
/// <param name="typeToConvert">The type being converted (should be <see cref="Hex"/>?).</param>
128+
/// <param name="options">The JSON serializer options.</param>
129+
/// <returns>A nullable <see cref="Hex"/> value parsed from the JSON, or null if the JSON token is null.</returns>
130+
/// <exception cref="JsonException">Thrown when the JSON token is not a string or null, or when the hex string is invalid.</exception>
131+
public override Hex? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
132+
{
133+
if (reader.TokenType == JsonTokenType.Null)
134+
{
135+
return null;
136+
}
137+
138+
if (reader.TokenType != JsonTokenType.String)
139+
{
140+
throw new JsonException($"Expected string or null token for nullable Hex deserialization, but got {reader.TokenType}");
141+
}
142+
143+
string? hexString = reader.GetString();
144+
145+
if (hexString == null)
146+
{
147+
return null;
148+
}
149+
150+
try
151+
{
152+
return Hex.Parse(hexString);
153+
}
154+
catch (ArgumentException ex)
155+
{
156+
throw new JsonException($"Invalid hex string '{hexString}': {ex.Message}", ex);
157+
}
158+
catch (FormatException ex)
159+
{
160+
throw new JsonException($"Invalid hex string format '{hexString}': {ex.Message}", ex);
161+
}
162+
}
163+
164+
/// <summary>
165+
/// Writes a nullable <see cref="Hex"/> value to JSON.
166+
/// </summary>
167+
/// <param name="writer">The JSON writer to write to.</param>
168+
/// <param name="value">The nullable <see cref="Hex"/> value to serialize.</param>
169+
/// <param name="options">The JSON serializer options.</param>
170+
public override void Write(Utf8JsonWriter writer, Hex? value, JsonSerializerOptions options)
171+
{
172+
if (value.HasValue)
173+
{
174+
writer.WriteStringValue(value.Value.ToString());
175+
}
176+
else
177+
{
178+
writer.WriteNullValue();
179+
}
180+
}
181+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
using System.Text.Json;
2+
3+
namespace Evoq.Blockchain;
4+
5+
/// <summary>
6+
/// Extension methods for <see cref="JsonSerializerOptions"/> to simplify Hex serialization configuration.
7+
/// </summary>
8+
public static class JsonSerializerOptionsExtensions
9+
{
10+
/// <summary>
11+
/// Configures the <see cref="JsonSerializerOptions"/> to automatically handle <see cref="Hex"/> serialization and deserialization.
12+
/// </summary>
13+
/// <param name="options">The <see cref="JsonSerializerOptions"/> to configure.</param>
14+
/// <returns>The same <see cref="JsonSerializerOptions"/> instance for method chaining.</returns>
15+
/// <remarks>
16+
/// <para>
17+
/// This method adds the necessary JSON converters to enable seamless serialization and deserialization of <see cref="Hex"/>
18+
/// values in your DTOs. After calling this method, <see cref="Hex"/> properties will be automatically converted to/from
19+
/// hex strings in JSON.
20+
/// </para>
21+
///
22+
/// <para>
23+
/// <strong>Usage:</strong>
24+
/// </para>
25+
///
26+
/// <code>
27+
/// var options = new JsonSerializerOptions().ConfigureForHex();
28+
///
29+
/// // Or chain with other configurations
30+
/// var options = new JsonSerializerOptions
31+
/// {
32+
/// PropertyNamingPolicy = JsonNamingPolicy.CamelCase
33+
/// }.ConfigureForHex();
34+
///
35+
/// // Now you can serialize/deserialize objects with Hex properties
36+
/// string json = JsonSerializer.Serialize(myObject, options);
37+
/// var result = JsonSerializer.Deserialize&lt;MyType&gt;(json, options);
38+
/// </code>
39+
///
40+
/// <para>
41+
/// <strong>What it configures:</strong>
42+
/// </para>
43+
/// <list type="bullet">
44+
/// <item><description><see cref="HexJsonConverter"/> for <see cref="Hex"/> properties</description></item>
45+
/// <item><description><see cref="NullableHexJsonConverter"/> for nullable <see cref="Hex"/> properties</description></item>
46+
/// </list>
47+
/// </remarks>
48+
public static JsonSerializerOptions ConfigureForHex(this JsonSerializerOptions options)
49+
{
50+
options.Converters.Add(new HexJsonConverter());
51+
options.Converters.Add(new NullableHexJsonConverter());
52+
return options;
53+
}
54+
}

0 commit comments

Comments
 (0)