1+ using System . Diagnostics . CodeAnalysis ;
2+ using System . Text . Json ;
3+ using System . Text . Json . Serialization ;
4+
5+ namespace A2A ;
6+
7+ /// <summary>
8+ /// Represents a JSON-RPC ID that can be either a string or a number, preserving the original type.
9+ /// </summary>
10+ [ JsonConverter ( typeof ( JsonRpcIdConverter ) ) ]
11+ public readonly struct JsonRpcId : IEquatable < JsonRpcId >
12+ {
13+ private readonly object ? _value ;
14+
15+ /// <summary>
16+ /// Initializes a new instance of the <see cref="JsonRpcId"/> struct with a string value.
17+ /// </summary>
18+ /// <param name="value">The string value.</param>
19+ public JsonRpcId ( string ? value )
20+ {
21+ _value = value ;
22+ }
23+
24+ /// <summary>
25+ /// Initializes a new instance of the <see cref="JsonRpcId"/> struct with a numeric value.
26+ /// </summary>
27+ /// <param name="value">The numeric value.</param>
28+ public JsonRpcId ( long value )
29+ {
30+ _value = value ;
31+ }
32+
33+ /// <summary>
34+ /// Initializes a new instance of the <see cref="JsonRpcId"/> struct with a numeric value.
35+ /// </summary>
36+ /// <param name="value">The numeric value.</param>
37+ public JsonRpcId ( int value )
38+ {
39+ _value = ( long ) value ;
40+ }
41+
42+ /// <summary>
43+ /// Gets a value indicating whether this ID has a value.
44+ /// </summary>
45+ public bool HasValue => _value is not null ;
46+
47+ /// <summary>
48+ /// Gets a value indicating whether this ID is a string.
49+ /// </summary>
50+ public bool IsString => _value is string ;
51+
52+ /// <summary>
53+ /// Gets a value indicating whether this ID is a number.
54+ /// </summary>
55+ public bool IsNumber => _value is long ;
56+
57+ /// <summary>
58+ /// Gets the string value of this ID if it's a string.
59+ /// </summary>
60+ /// <returns>The string value, or null if not a string.</returns>
61+ public string ? AsString ( ) => _value as string ;
62+
63+ /// <summary>
64+ /// Gets the numeric value of this ID if it's a number.
65+ /// </summary>
66+ /// <returns>The numeric value, or null if not a number.</returns>
67+ public long ? AsNumber ( ) => _value as long ? ;
68+
69+ /// <summary>
70+ /// Gets the raw value as an object.
71+ /// </summary>
72+ /// <returns>The raw value as an object.</returns>
73+ public object ? AsObject ( ) => _value ;
74+
75+ /// <summary>
76+ /// Returns a string representation of this ID.
77+ /// </summary>
78+ /// <returns>A string representation of the ID.</returns>
79+ public override string ? ToString ( ) => _value ? . ToString ( ) ;
80+
81+ /// <summary>
82+ /// Determines whether the specified object is equal to the current ID.
83+ /// </summary>
84+ /// <param name="obj">The object to compare with the current ID.</param>
85+ /// <returns>true if the specified object is equal to the current ID; otherwise, false.</returns>
86+ public override bool Equals ( object ? obj ) => obj is JsonRpcId other && Equals ( other ) ;
87+
88+ /// <summary>
89+ /// Determines whether the specified ID is equal to the current ID.
90+ /// </summary>
91+ /// <param name="other">The ID to compare with the current ID.</param>
92+ /// <returns>true if the specified ID is equal to the current ID; otherwise, false.</returns>
93+ public bool Equals ( JsonRpcId other ) => Equals ( _value , other . _value ) ;
94+
95+ /// <summary>
96+ /// Returns the hash code for this ID.
97+ /// </summary>
98+ /// <returns>A hash code for the current ID.</returns>
99+ public override int GetHashCode ( ) => _value ? . GetHashCode ( ) ?? 0 ;
100+
101+ /// <summary>
102+ /// Determines whether two IDs are equal.
103+ /// </summary>
104+ /// <param name="left">The first ID to compare.</param>
105+ /// <param name="right">The second ID to compare.</param>
106+ /// <returns>true if the IDs are equal; otherwise, false.</returns>
107+ public static bool operator == ( JsonRpcId left , JsonRpcId right ) => left . Equals ( right ) ;
108+
109+ /// <summary>
110+ /// Determines whether two IDs are not equal.
111+ /// </summary>
112+ /// <param name="left">The first ID to compare.</param>
113+ /// <param name="right">The second ID to compare.</param>
114+ /// <returns>true if the IDs are not equal; otherwise, false.</returns>
115+ public static bool operator != ( JsonRpcId left , JsonRpcId right ) => ! left . Equals ( right ) ;
116+
117+ /// <summary>
118+ /// Implicitly converts a string to a JsonRpcId.
119+ /// </summary>
120+ /// <param name="value">The string value.</param>
121+ /// <returns>A JsonRpcId with the string value.</returns>
122+ public static implicit operator JsonRpcId ( string ? value ) => new ( value ) ;
123+
124+ /// <summary>
125+ /// Implicitly converts a long to a JsonRpcId.
126+ /// </summary>
127+ /// <param name="value">The numeric value.</param>
128+ /// <returns>A JsonRpcId with the numeric value.</returns>
129+ public static implicit operator JsonRpcId ( long value ) => new ( value ) ;
130+
131+ /// <summary>
132+ /// Implicitly converts an int to a JsonRpcId.
133+ /// </summary>
134+ /// <param name="value">The numeric value.</param>
135+ /// <returns>A JsonRpcId with the numeric value.</returns>
136+ public static implicit operator JsonRpcId ( int value ) => new ( value ) ;
137+ }
138+
139+ /// <summary>
140+ /// JSON converter for JsonRpcId that preserves the original type during serialization/deserialization.
141+ /// </summary>
142+ internal sealed class JsonRpcIdConverter : JsonConverter < JsonRpcId >
143+ {
144+ public override JsonRpcId Read ( ref Utf8JsonReader reader , Type typeToConvert , JsonSerializerOptions options )
145+ {
146+ switch ( reader . TokenType )
147+ {
148+ case JsonTokenType . String :
149+ return new JsonRpcId ( reader . GetString ( ) ) ;
150+ case JsonTokenType . Number :
151+ if ( reader . TryGetInt64 ( out var longValue ) )
152+ {
153+ return new JsonRpcId ( longValue ) ;
154+ }
155+ throw new JsonException ( "Invalid numeric value for JSON-RPC ID." ) ;
156+ case JsonTokenType . Null :
157+ return new JsonRpcId ( ( string ? ) null ) ;
158+ default :
159+ throw new JsonException ( "Invalid JSON-RPC ID format. Must be string, number, or null." ) ;
160+ }
161+ }
162+
163+ public override void Write ( Utf8JsonWriter writer , JsonRpcId value , JsonSerializerOptions options )
164+ {
165+ if ( ! value . HasValue )
166+ {
167+ writer . WriteNullValue ( ) ;
168+ }
169+ else if ( value . IsString )
170+ {
171+ writer . WriteStringValue ( value . AsString ( ) ) ;
172+ }
173+ else if ( value . IsNumber )
174+ {
175+ writer . WriteNumberValue ( value . AsNumber ( ) ! . Value ) ;
176+ }
177+ else
178+ {
179+ writer . WriteNullValue ( ) ;
180+ }
181+ }
182+ }
0 commit comments