Skip to content

Commit 5d3414d

Browse files
authored
[dotnet] Make classic WebDriver commands/responses AOT compatible (#14574)
1 parent ddfb3d8 commit 5d3414d

File tree

3 files changed

+134
-63
lines changed

3 files changed

+134
-63
lines changed

dotnet/src/webdriver/Command.cs

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,15 +28,16 @@ namespace OpenQA.Selenium
2828
/// </summary>
2929
public class Command
3030
{
31+
private SessionId commandSessionId;
32+
private string commandName;
33+
private Dictionary<string, object> commandParameters = new Dictionary<string, object>();
34+
3135
private readonly static JsonSerializerOptions s_jsonSerializerOptions = new()
3236
{
37+
TypeInfoResolver = CommandJsonSerializerContext.Default,
3338
Converters = { new ResponseValueJsonConverter() }
3439
};
3540

36-
private SessionId commandSessionId;
37-
private string commandName;
38-
private Dictionary<string, object> commandParameters = new Dictionary<string, object>();
39-
4041
/// <summary>
4142
/// Initializes a new instance of the <see cref="Command"/> class using a command name and a JSON-encoded string for the parameters.
4243
/// </summary>
@@ -101,7 +102,7 @@ public string ParametersAsJsonString
101102
string parametersString = string.Empty;
102103
if (this.commandParameters != null && this.commandParameters.Count > 0)
103104
{
104-
parametersString = JsonSerializer.Serialize(this.commandParameters);
105+
parametersString = JsonSerializer.Serialize(this.commandParameters, s_jsonSerializerOptions);
105106
}
106107

107108
if (string.IsNullOrEmpty(parametersString))
@@ -133,4 +134,34 @@ private static Dictionary<string, object> ConvertParametersFromJson(string value
133134
return parameters;
134135
}
135136
}
137+
138+
// Built-in types
139+
[JsonSerializable(typeof(bool))]
140+
[JsonSerializable(typeof(byte))]
141+
[JsonSerializable(typeof(sbyte))]
142+
[JsonSerializable(typeof(char))]
143+
[JsonSerializable(typeof(decimal))]
144+
[JsonSerializable(typeof(double))]
145+
[JsonSerializable(typeof(float))]
146+
[JsonSerializable(typeof(int))]
147+
[JsonSerializable(typeof(uint))]
148+
[JsonSerializable(typeof(nint))]
149+
[JsonSerializable(typeof(nuint))]
150+
[JsonSerializable(typeof(long))]
151+
[JsonSerializable(typeof(ulong))]
152+
[JsonSerializable(typeof(short))]
153+
[JsonSerializable(typeof(ushort))]
154+
155+
[JsonSerializable(typeof(string))]
156+
157+
// Selenium WebDriver types
158+
[JsonSerializable(typeof(char[]))]
159+
[JsonSerializable(typeof(byte[]))]
160+
[JsonSerializable(typeof(Dictionary<string, object>))]
161+
[JsonSerializable(typeof(Cookie))]
162+
[JsonSerializable(typeof(Proxy))]
163+
internal partial class CommandJsonSerializerContext : JsonSerializerContext
164+
{
165+
166+
}
136167
}

dotnet/src/webdriver/Internal/ResponseValueJsonConverter.cs

Lines changed: 89 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -30,75 +30,107 @@ internal class ResponseValueJsonConverter : JsonConverter<object>
3030
{
3131
public override object Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
3232
{
33-
return this.ProcessToken(ref reader, options);
33+
return ProcessReadToken(ref reader, options);
3434
}
3535

3636
public override void Write(Utf8JsonWriter writer, object value, JsonSerializerOptions options)
3737
{
38-
JsonSerializer.Serialize(writer, value, options);
38+
switch (value)
39+
{
40+
case null:
41+
writer.WriteNullValue();
42+
break;
43+
case Enum:
44+
writer.WriteNumberValue(Convert.ToInt64(value));
45+
break;
46+
case IEnumerable<object> list:
47+
writer.WriteStartArray();
48+
foreach (var item in list)
49+
{
50+
Write(writer, item, options);
51+
}
52+
writer.WriteEndArray();
53+
break;
54+
case IDictionary<string, object> dictionary:
55+
writer.WriteStartObject();
56+
foreach (var pair in dictionary)
57+
{
58+
writer.WritePropertyName(pair.Key);
59+
Write(writer, pair.Value, options);
60+
}
61+
writer.WriteEndObject();
62+
break;
63+
case object obj:
64+
JsonSerializer.Serialize(writer, obj, options.GetTypeInfo(obj.GetType()));
65+
break;
66+
}
3967
}
4068

41-
private object ProcessToken(ref Utf8JsonReader reader, JsonSerializerOptions options)
69+
private static object ProcessReadToken(ref Utf8JsonReader reader, JsonSerializerOptions options)
4270
{
4371
// Recursively processes a token. This is required for elements that next other elements.
44-
object processedObject = null;
72+
object processedObject;
4573

46-
if (reader.TokenType == JsonTokenType.StartObject)
74+
switch (reader.TokenType)
4775
{
48-
Dictionary<string, object> dictionaryValue = new Dictionary<string, object>();
49-
while (reader.Read() && reader.TokenType != JsonTokenType.EndObject)
50-
{
51-
string elementKey = reader.GetString();
52-
reader.Read();
53-
dictionaryValue.Add(elementKey, this.ProcessToken(ref reader, options));
54-
}
76+
case JsonTokenType.StartObject:
77+
{
78+
Dictionary<string, object> dictionaryValue = [];
79+
while (reader.Read() && reader.TokenType != JsonTokenType.EndObject)
80+
{
81+
string elementKey = reader.GetString();
82+
reader.Read();
83+
dictionaryValue.Add(elementKey, ProcessReadToken(ref reader, options));
84+
}
5585

56-
processedObject = dictionaryValue;
57-
}
58-
else if (reader.TokenType == JsonTokenType.StartArray)
59-
{
60-
List<object> arrayValue = new List<object>();
61-
while (reader.Read() && reader.TokenType != JsonTokenType.EndArray)
62-
{
63-
arrayValue.Add(this.ProcessToken(ref reader, options));
64-
}
86+
processedObject = dictionaryValue;
87+
break;
88+
}
6589

66-
processedObject = arrayValue.ToArray();
67-
}
68-
else if (reader.TokenType == JsonTokenType.Null)
69-
{
70-
processedObject = null;
71-
}
72-
else if (reader.TokenType == JsonTokenType.False)
73-
{
74-
processedObject = false;
75-
}
76-
else if (reader.TokenType == JsonTokenType.True)
77-
{
78-
processedObject = true;
79-
}
80-
else if (reader.TokenType == JsonTokenType.String)
81-
{
82-
processedObject = reader.GetString();
83-
}
84-
else if (reader.TokenType == JsonTokenType.Number)
85-
{
86-
if (reader.TryGetInt64(out long longValue))
87-
{
88-
processedObject = longValue;
89-
}
90-
else if (reader.TryGetDouble(out double doubleValue))
91-
{
92-
processedObject = doubleValue;
93-
}
94-
else
95-
{
96-
throw new JsonException($"Unrecognized '{JsonElement.ParseValue(ref reader)}' token as a number value.");
97-
}
98-
}
99-
else
100-
{
101-
throw new JsonException($"Unrecognized '{reader.TokenType}' token type while parsing command response.");
90+
case JsonTokenType.StartArray:
91+
{
92+
List<object> arrayValue = [];
93+
while (reader.Read() && reader.TokenType != JsonTokenType.EndArray)
94+
{
95+
arrayValue.Add(ProcessReadToken(ref reader, options));
96+
}
97+
98+
processedObject = arrayValue.ToArray();
99+
break;
100+
}
101+
102+
case JsonTokenType.Null:
103+
processedObject = null;
104+
break;
105+
case JsonTokenType.False:
106+
processedObject = false;
107+
break;
108+
case JsonTokenType.True:
109+
processedObject = true;
110+
break;
111+
case JsonTokenType.String:
112+
processedObject = reader.GetString();
113+
break;
114+
case JsonTokenType.Number:
115+
{
116+
if (reader.TryGetInt64(out long longValue))
117+
{
118+
processedObject = longValue;
119+
}
120+
else if (reader.TryGetDouble(out double doubleValue))
121+
{
122+
processedObject = doubleValue;
123+
}
124+
else
125+
{
126+
throw new JsonException($"Unrecognized '{JsonElement.ParseValue(ref reader)}' token as a number value.");
127+
}
128+
129+
break;
130+
}
131+
132+
default:
133+
throw new JsonException($"Unrecognized '{reader.TokenType}' token type while parsing command response.");
102134
}
103135

104136
return processedObject;

dotnet/src/webdriver/Response.cs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
using System.Collections.Generic;
2222
using System.Globalization;
2323
using System.Text.Json;
24+
using System.Text.Json.Serialization;
2425

2526
namespace OpenQA.Selenium
2627
{
@@ -31,7 +32,8 @@ public class Response
3132
{
3233
private readonly static JsonSerializerOptions s_jsonSerializerOptions = new()
3334
{
34-
Converters = { new ResponseValueJsonConverter() }
35+
TypeInfoResolver = ResponseJsonSerializerContext.Default,
36+
Converters = { new ResponseValueJsonConverter() } // we still need it to make `Object` as `Dictionary`
3537
};
3638

3739
private object responseValue;
@@ -208,4 +210,10 @@ public override string ToString()
208210
return string.Format(CultureInfo.InvariantCulture, "({0} {1}: {2})", this.SessionId, this.Status, this.Value);
209211
}
210212
}
213+
214+
[JsonSerializable(typeof(Dictionary<string, object>))]
215+
internal partial class ResponseJsonSerializerContext : JsonSerializerContext
216+
{
217+
218+
}
211219
}

0 commit comments

Comments
 (0)