diff --git a/dotnet/src/webdriver/Command.cs b/dotnet/src/webdriver/Command.cs index d12274ca90d7a..409c393860d6c 100644 --- a/dotnet/src/webdriver/Command.cs +++ b/dotnet/src/webdriver/Command.cs @@ -28,15 +28,16 @@ namespace OpenQA.Selenium /// public class Command { + private SessionId commandSessionId; + private string commandName; + private Dictionary commandParameters = new Dictionary(); + private readonly static JsonSerializerOptions s_jsonSerializerOptions = new() { + TypeInfoResolver = CommandJsonSerializerContext.Default, Converters = { new ResponseValueJsonConverter() } }; - private SessionId commandSessionId; - private string commandName; - private Dictionary commandParameters = new Dictionary(); - /// /// Initializes a new instance of the class using a command name and a JSON-encoded string for the parameters. /// @@ -101,7 +102,7 @@ public string ParametersAsJsonString string parametersString = string.Empty; if (this.commandParameters != null && this.commandParameters.Count > 0) { - parametersString = JsonSerializer.Serialize(this.commandParameters); + parametersString = JsonSerializer.Serialize(this.commandParameters, s_jsonSerializerOptions); } if (string.IsNullOrEmpty(parametersString)) @@ -133,4 +134,34 @@ private static Dictionary ConvertParametersFromJson(string value return parameters; } } + + // Built-in types + [JsonSerializable(typeof(bool))] + [JsonSerializable(typeof(byte))] + [JsonSerializable(typeof(sbyte))] + [JsonSerializable(typeof(char))] + [JsonSerializable(typeof(decimal))] + [JsonSerializable(typeof(double))] + [JsonSerializable(typeof(float))] + [JsonSerializable(typeof(int))] + [JsonSerializable(typeof(uint))] + [JsonSerializable(typeof(nint))] + [JsonSerializable(typeof(nuint))] + [JsonSerializable(typeof(long))] + [JsonSerializable(typeof(ulong))] + [JsonSerializable(typeof(short))] + [JsonSerializable(typeof(ushort))] + + [JsonSerializable(typeof(string))] + + // Selenium WebDriver types + [JsonSerializable(typeof(char[]))] + [JsonSerializable(typeof(byte[]))] + [JsonSerializable(typeof(Dictionary))] + [JsonSerializable(typeof(Cookie))] + [JsonSerializable(typeof(Proxy))] + internal partial class CommandJsonSerializerContext : JsonSerializerContext + { + + } } diff --git a/dotnet/src/webdriver/Internal/ResponseValueJsonConverter.cs b/dotnet/src/webdriver/Internal/ResponseValueJsonConverter.cs index 9967d790c55c9..6b888a788236b 100644 --- a/dotnet/src/webdriver/Internal/ResponseValueJsonConverter.cs +++ b/dotnet/src/webdriver/Internal/ResponseValueJsonConverter.cs @@ -30,75 +30,107 @@ internal class ResponseValueJsonConverter : JsonConverter { public override object Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - return this.ProcessToken(ref reader, options); + return ProcessReadToken(ref reader, options); } public override void Write(Utf8JsonWriter writer, object value, JsonSerializerOptions options) { - JsonSerializer.Serialize(writer, value, options); + switch (value) + { + case null: + writer.WriteNullValue(); + break; + case Enum: + writer.WriteNumberValue(Convert.ToInt64(value)); + break; + case IEnumerable list: + writer.WriteStartArray(); + foreach (var item in list) + { + Write(writer, item, options); + } + writer.WriteEndArray(); + break; + case IDictionary dictionary: + writer.WriteStartObject(); + foreach (var pair in dictionary) + { + writer.WritePropertyName(pair.Key); + Write(writer, pair.Value, options); + } + writer.WriteEndObject(); + break; + case object obj: + JsonSerializer.Serialize(writer, obj, options.GetTypeInfo(obj.GetType())); + break; + } } - private object ProcessToken(ref Utf8JsonReader reader, JsonSerializerOptions options) + private static object ProcessReadToken(ref Utf8JsonReader reader, JsonSerializerOptions options) { // Recursively processes a token. This is required for elements that next other elements. - object processedObject = null; + object processedObject; - if (reader.TokenType == JsonTokenType.StartObject) + switch (reader.TokenType) { - Dictionary dictionaryValue = new Dictionary(); - while (reader.Read() && reader.TokenType != JsonTokenType.EndObject) - { - string elementKey = reader.GetString(); - reader.Read(); - dictionaryValue.Add(elementKey, this.ProcessToken(ref reader, options)); - } + case JsonTokenType.StartObject: + { + Dictionary dictionaryValue = []; + while (reader.Read() && reader.TokenType != JsonTokenType.EndObject) + { + string elementKey = reader.GetString(); + reader.Read(); + dictionaryValue.Add(elementKey, ProcessReadToken(ref reader, options)); + } - processedObject = dictionaryValue; - } - else if (reader.TokenType == JsonTokenType.StartArray) - { - List arrayValue = new List(); - while (reader.Read() && reader.TokenType != JsonTokenType.EndArray) - { - arrayValue.Add(this.ProcessToken(ref reader, options)); - } + processedObject = dictionaryValue; + break; + } - processedObject = arrayValue.ToArray(); - } - else if (reader.TokenType == JsonTokenType.Null) - { - processedObject = null; - } - else if (reader.TokenType == JsonTokenType.False) - { - processedObject = false; - } - else if (reader.TokenType == JsonTokenType.True) - { - processedObject = true; - } - else if (reader.TokenType == JsonTokenType.String) - { - processedObject = reader.GetString(); - } - else if (reader.TokenType == JsonTokenType.Number) - { - if (reader.TryGetInt64(out long longValue)) - { - processedObject = longValue; - } - else if (reader.TryGetDouble(out double doubleValue)) - { - processedObject = doubleValue; - } - else - { - throw new JsonException($"Unrecognized '{JsonElement.ParseValue(ref reader)}' token as a number value."); - } - } - else - { - throw new JsonException($"Unrecognized '{reader.TokenType}' token type while parsing command response."); + case JsonTokenType.StartArray: + { + List arrayValue = []; + while (reader.Read() && reader.TokenType != JsonTokenType.EndArray) + { + arrayValue.Add(ProcessReadToken(ref reader, options)); + } + + processedObject = arrayValue.ToArray(); + break; + } + + case JsonTokenType.Null: + processedObject = null; + break; + case JsonTokenType.False: + processedObject = false; + break; + case JsonTokenType.True: + processedObject = true; + break; + case JsonTokenType.String: + processedObject = reader.GetString(); + break; + case JsonTokenType.Number: + { + if (reader.TryGetInt64(out long longValue)) + { + processedObject = longValue; + } + else if (reader.TryGetDouble(out double doubleValue)) + { + processedObject = doubleValue; + } + else + { + throw new JsonException($"Unrecognized '{JsonElement.ParseValue(ref reader)}' token as a number value."); + } + + break; + } + + default: + throw new JsonException($"Unrecognized '{reader.TokenType}' token type while parsing command response."); } return processedObject; diff --git a/dotnet/src/webdriver/Response.cs b/dotnet/src/webdriver/Response.cs index 674a6488135b4..ebfd47e2cfa1b 100644 --- a/dotnet/src/webdriver/Response.cs +++ b/dotnet/src/webdriver/Response.cs @@ -21,6 +21,7 @@ using System.Collections.Generic; using System.Globalization; using System.Text.Json; +using System.Text.Json.Serialization; namespace OpenQA.Selenium { @@ -31,7 +32,8 @@ public class Response { private readonly static JsonSerializerOptions s_jsonSerializerOptions = new() { - Converters = { new ResponseValueJsonConverter() } + TypeInfoResolver = ResponseJsonSerializerContext.Default, + Converters = { new ResponseValueJsonConverter() } // we still need it to make `Object` as `Dictionary` }; private object responseValue; @@ -208,4 +210,10 @@ public override string ToString() return string.Format(CultureInfo.InvariantCulture, "({0} {1}: {2})", this.SessionId, this.Status, this.Value); } } + + [JsonSerializable(typeof(Dictionary))] + internal partial class ResponseJsonSerializerContext : JsonSerializerContext + { + + } }