/*
* Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
* See https://github.com/openiddict/openiddict-core for more information concerning
* the license and the contributors participating to this project.
*/
using System.Collections.Immutable;
using System.ComponentModel;
using System.Globalization;
using System.Text.Encodings.Web;
using System.Text.Json;
#if SUPPORTS_JSON_NODES
using System.Text.Json.Nodes;
#endif
namespace OpenIddict.Abstractions;
///
/// Represents an OpenIddict parameter value, that can be either a primitive value,
/// an array of strings or a complex JSON representation containing child nodes.
///
public readonly struct OpenIddictParameter : IEquatable
{
///
/// Initializes a new parameter using the specified value.
///
/// The parameter value.
public OpenIddictParameter(bool value) => Value = value;
///
/// Initializes a new parameter using the specified value.
///
/// The parameter value.
public OpenIddictParameter(bool? value) => Value = value;
///
/// Initializes a new parameter using the specified value.
///
/// The parameter value.
public OpenIddictParameter(JsonElement value) => Value = value;
#if SUPPORTS_JSON_NODES
///
/// Initializes a new parameter using the specified value.
///
/// The parameter value.
public OpenIddictParameter(JsonNode? value) => Value = value;
#endif
///
/// Initializes a new parameter using the specified value.
///
/// The parameter value.
public OpenIddictParameter(long value) => Value = value;
///
/// Initializes a new parameter using the specified value.
///
/// The parameter value.
public OpenIddictParameter(long? value) => Value = value;
///
/// Initializes a new parameter using the specified value.
///
/// The parameter value.
public OpenIddictParameter(string? value) => Value = value;
///
/// Initializes a new parameter using the specified value.
///
/// The parameter value.
public OpenIddictParameter(string?[]? value) => Value = value;
///
/// Gets the child item corresponding to the specified index.
///
/// The index of the child item.
/// An instance containing the item value.
public OpenIddictParameter? this[int index] => GetUnnamedParameter(index);
///
/// Gets the child item corresponding to the specified name.
///
/// The name of the child item.
/// An instance containing the item value.
public OpenIddictParameter? this[string name] => GetNamedParameter(name);
///
/// Gets the number of named or unnamed child items contained in the current parameter or 0
/// if the parameter doesn't represent an array of strings, a JSON array or a JSON object.
///
public int Count
{
get
{
return Value switch
{
// If the parameter is a primitive array of strings, return its length.
string?[] value => value.Length,
// If the parameter is a JSON array or a JSON object, return its length.
JsonElement { ValueKind: JsonValueKind.Array or JsonValueKind.Object } element
=> Count(element),
#if SUPPORTS_JSON_NODES
// If the parameter is a JsonArray, return its length.
JsonArray value => value.Count,
// If the parameter is a JsonObject, return its length.
JsonObject value => value.Count,
// If the parameter is any other JsonNode (e.g a JsonValue), serialize it
// to a JsonElement first to determine its actual JSON representation
// and extract the number of items if the element is a JSON array or object.
JsonNode value when JsonSerializer.SerializeToElement(value)
is JsonElement { ValueKind: JsonValueKind.Array or JsonValueKind.Object } element
=> Count(element),
#endif
// Otherwise, return 0.
_ => 0
};
static int Count(JsonElement element)
{
switch (element.ValueKind)
{
case JsonValueKind.Array:
return element.GetArrayLength();
case JsonValueKind.Object:
var count = 0;
using (var enumerator = element.EnumerateObject())
{
checked
{
while (enumerator.MoveNext())
{
count++;
}
}
}
return count;
default: return 0;
}
}
}
}
///
/// Gets the associated value, that can be either a primitive CLR type
/// (e.g bool, string, long), an array of strings or a complex JSON object.
///
[EditorBrowsable(EditorBrowsableState.Advanced)]
public object? Value { get; }
///
/// Determines whether the current
/// instance is equal to the specified .
///
/// The other object to which to compare this instance.
///
/// if the two instances have both the same representation
/// (e.g ) and value, otherwise.
///
public bool Equals(OpenIddictParameter other)
{
return (left: Value, right: other.Value) switch
{
// If the two parameters reference the same instance, return true.
//
// Note: true will also be returned if the two parameters are null.
var (left, right) when ReferenceEquals(left, right) => true,
// If one of the two parameters is null, return false.
(null, _) or (_, null) => false,
// If the two parameters are booleans, compare them directly.
(bool left, bool right) => left == right,
// If the two parameters are integers, compare them directly.
(long left, long right) => left == right,
// If the two parameters are strings, use string.Equals().
(string left, string right) => string.Equals(left, right, StringComparison.Ordinal),
// If the two parameters are string arrays, use SequenceEqual().
(string?[] left, string?[] right) => left.SequenceEqual(right),
// If one of the two parameters is an undefined JsonElement, treat it
// as a null value and return true if the other parameter is null too.
(JsonElement { ValueKind: JsonValueKind.Null or JsonValueKind.Undefined }, var right)
=> right is null,
(var left, JsonElement { ValueKind: JsonValueKind.Null or JsonValueKind.Undefined })
=> left is null,
// If the two parameters are JsonElement instances, use the custom comparer.
(JsonElement left, JsonElement right) => DeepEquals(left, right),
// When one of the parameters is a JsonElement, compare their underlying values.
(JsonElement { ValueKind: JsonValueKind.True }, bool right) => right,
(bool left, JsonElement { ValueKind: JsonValueKind.True }) => left,
(JsonElement { ValueKind: JsonValueKind.False }, bool right) => !right,
(bool left, JsonElement { ValueKind: JsonValueKind.False }) => !left,
(JsonElement { ValueKind: JsonValueKind.Number } left, long right)
when left.TryGetInt64(out var result) => result == right,
(long left, JsonElement { ValueKind: JsonValueKind.Number } right)
when right.TryGetInt64(out var result) => left == result,
(JsonElement { ValueKind: JsonValueKind.String } left, string right)
=> string.Equals(left.GetString(), right, StringComparison.Ordinal),
(string left, JsonElement { ValueKind: JsonValueKind.String } right)
=> string.Equals(left, right.GetString(), StringComparison.Ordinal),
#if SUPPORTS_JSON_NODES
// When one of the parameters is a JsonValue, try to compare their underlying values
// if the wrapped type is a common CLR primitive type to avoid the less efficient
// JsonElement-based comparison, that requires doing a full JSON serialization.
(JsonValue left, bool right) when left.TryGetValue(out bool result) => result == right,
(bool left, JsonValue right) when right.TryGetValue(out bool result) => result == left,
(JsonValue left, long right) when left.TryGetValue(out int result) => result == right,
(long left, JsonValue right) when right.TryGetValue(out int result) => result == left,
(JsonValue left, long right) when left.TryGetValue(out long result) => result == right,
(long left, JsonValue right) when right.TryGetValue(out long result) => result == left,
(JsonValue left, string right) when left.TryGetValue(out string? result)
=> string.Equals(result, right, StringComparison.Ordinal),
(string left, JsonValue right) when right.TryGetValue(out string? result)
=> string.Equals(left, result, StringComparison.Ordinal),
#endif
// Otherwise, serialize both values to JsonElement and compare them.
#if SUPPORTS_DIRECT_JSON_ELEMENT_SERIALIZATION
var (left, right) => DeepEquals(
JsonSerializer.SerializeToElement(left, left.GetType()),
JsonSerializer.SerializeToElement(right, right.GetType()))
#else
var (left, right) => DeepEquals(
JsonSerializer.Deserialize(JsonSerializer.Serialize(left, left.GetType())),
JsonSerializer.Deserialize(JsonSerializer.Serialize(right, right.GetType())))
#endif
};
static bool DeepEquals(JsonElement left, JsonElement right)
{
switch ((left.ValueKind, right.ValueKind))
{
case (JsonValueKind.Undefined, JsonValueKind.Undefined):
case (JsonValueKind.Null, JsonValueKind.Null):
case (JsonValueKind.False, JsonValueKind.False):
case (JsonValueKind.True, JsonValueKind.True):
return true;
// Treat undefined JsonElement instances as null values.
case (JsonValueKind.Undefined, JsonValueKind.Null):
case (JsonValueKind.Null, JsonValueKind.Undefined):
return true;
case (JsonValueKind.Number, JsonValueKind.Number):
return string.Equals(left.GetRawText(), right.GetRawText(), StringComparison.Ordinal);
case (JsonValueKind.String, JsonValueKind.String):
return string.Equals(left.GetString(), right.GetString(), StringComparison.Ordinal);
case (JsonValueKind.Array, JsonValueKind.Array):
{
var length = left.GetArrayLength();
if (length != right.GetArrayLength())
{
return false;
}
for (var index = 0; index < length; index++)
{
if (!DeepEquals(left[index], right[index]))
{
return false;
}
}
return true;
}
case (JsonValueKind.Object, JsonValueKind.Object):
{
foreach (var property in left.EnumerateObject())
{
if (!right.TryGetProperty(property.Name, out JsonElement element) ||
property.Value.ValueKind != element.ValueKind)
{
return false;
}
if (!DeepEquals(property.Value, element))
{
return false;
}
}
return true;
}
default: return false;
}
}
}
///
/// Determines whether the current
/// instance is equal to the specified .
///
/// The other object to which to compare this instance.
///
/// if the two instances have both the same representation
/// (e.g ) and value, otherwise.
///
public override bool Equals(object? obj) => obj is OpenIddictParameter parameter && Equals(parameter);
///
/// Returns the hash code of the current instance.
///
/// The hash code for the current instance.
public override int GetHashCode()
{
return Value switch
{
// When the parameter value is null, return 0.
null => 0,
// When the parameter is an array of strings, compute the hash code of its items to
// match the logic used when treating a JsonElement instance representing an array.
string?[] value => GetHashCodeFromArray(value),
// When the parameter is a JsonElement, compute its hash code.
JsonElement value => GetHashCodeFromJsonElement(value),
#if SUPPORTS_JSON_NODES
// When the parameter is a JsonValue wrapping a JsonElement,
// apply the same logic as with direct JsonElement instances.
JsonValue value when value.TryGetValue(out JsonElement element)
=> GetHashCodeFromJsonElement(element),
// When the parameter is a JsonValue, compute the hash code of its underlying value
// if the wrapped type is a common CLR primitive type to avoid the less efficient
// JsonElement-based computation, that requires doing a full JSON serialization.
JsonValue value when value.TryGetValue(out bool result) => result.GetHashCode(),
JsonValue value when value.TryGetValue(out int result) => result.GetHashCode(),
JsonValue value when value.TryGetValue(out long result) => result.GetHashCode(),
JsonValue value when value.TryGetValue(out string? result) => result.GetHashCode(),
// When the parameter is a JsonNode (e.g a JsonValue wrapping a non-primitive type),
// serialize it to a JsonElement first to determine its actual JSON representation
// and apply the same logic as with non-wrapped JsonElement instances.
JsonNode value when JsonSerializer.SerializeToElement(value) is JsonElement element
=> GetHashCodeFromJsonElement(element),
#endif
// Otherwise, use the default hash code method.
var value => value.GetHashCode()
};
static int GetHashCodeFromArray(string?[] array)
{
var hash = new HashCode();
for (var index = 0; index < array.Length; index++)
{
hash.Add(array[index]);
}
return hash.ToHashCode();
}
static int GetHashCodeFromJsonElement(JsonElement element)
{
switch (element.ValueKind)
{
case JsonValueKind.Undefined:
case JsonValueKind.Null:
return 0;
case JsonValueKind.True:
return true.GetHashCode();
case JsonValueKind.False:
return false.GetHashCode();
case JsonValueKind.Number when element.TryGetInt64(out var result):
return result.GetHashCode();
case JsonValueKind.Number:
return element.GetRawText().GetHashCode();
case JsonValueKind.String:
return element.GetString()!.GetHashCode();
case JsonValueKind.Array:
{
var hash = new HashCode();
foreach (var item in element.EnumerateArray())
{
hash.Add(GetHashCodeFromJsonElement(item));
}
return hash.ToHashCode();
}
case JsonValueKind.Object:
{
var hash = new HashCode();
foreach (var property in element.EnumerateObject())
{
hash.Add(property.Name);
hash.Add(GetHashCodeFromJsonElement(property.Value));
}
return hash.ToHashCode();
}
default: return 0;
}
}
}
///
/// Gets the child item corresponding to the specified name.
///
/// The name of the child item.
/// An instance containing the item value.
public OpenIddictParameter? GetNamedParameter(string name)
=> TryGetNamedParameter(name, out var value) ? value : (OpenIddictParameter?) null;
///
/// Gets the child item corresponding to the specified index.
///
/// The index of the child item.
/// An instance containing the item value.
public OpenIddictParameter? GetUnnamedParameter(int index)
=> TryGetUnnamedParameter(index, out var value) ? value : (OpenIddictParameter?) null;
///
/// Gets the named child items associated with the current parameter, if it represents a JSON object.
/// Note: if the JSON object contains multiple parameters with the same name, only the last occurrence is returned.
///
/// A dictionary of all the parameters associated with the current instance.
public IReadOnlyDictionary GetNamedParameters()
{
return Value switch
{
// When the parameter is a JsonElement representing an object, return the requested item.
JsonElement { ValueKind: JsonValueKind.Object } value => GetParametersFromJsonElement(value),
#if SUPPORTS_JSON_NODES
// When the parameter is a JsonObject, return the requested item.
JsonObject value => GetParametersFromJsonNode(value),
// When the parameter is a JsonNode (e.g a JsonValue wrapping a non-primitive type),
// serialize it to a JsonElement first to determine its actual JSON representation
// and apply the same logic as with non-wrapped JsonElement instances.
JsonNode value when JsonSerializer.SerializeToElement(value)
is JsonElement { ValueKind: JsonValueKind.Object } element
=> GetParametersFromJsonElement(element),
#endif
_ => ImmutableDictionary.Create(StringComparer.Ordinal)
};
static IReadOnlyDictionary GetParametersFromJsonElement(JsonElement element)
{
var parameters = new Dictionary(StringComparer.Ordinal);
foreach (var property in element.EnumerateObject())
{
parameters[property.Name] = new(property.Value);
}
return parameters;
}
#if SUPPORTS_JSON_NODES
static IReadOnlyDictionary GetParametersFromJsonNode(JsonObject node)
{
var parameters = new Dictionary(node.Count, StringComparer.Ordinal);
foreach (var property in node)
{
parameters[property.Key] = new(property.Value);
}
return parameters;
}
#endif
}
///
/// Gets the unnamed child items associated with the current parameter,
/// if it represents an array of strings or a JSON array.
///
/// An enumeration of all the unnamed parameters associated with the current instance.
public IReadOnlyList GetUnnamedParameters()
{
return Value switch
{
// When the parameter is an array of strings, return its items.
string?[] value => GetParametersFromArray(value),
// When the parameter is a JsonElement representing an array, return its children.
JsonElement { ValueKind: JsonValueKind.Array } value => GetParametersFromJsonElement(value),
#if SUPPORTS_JSON_NODES
// When the parameter is a JsonArray, return its children.
JsonArray value => GetParametersFromJsonNode(value),
// When the parameter is a JsonNode (e.g a JsonValue wrapping a non-primitive type),
// serialize it to a JsonElement first to determine its actual JSON representation
// and apply the same logic as with non-wrapped JsonElement instances.
JsonNode value when JsonSerializer.SerializeToElement(value)
is JsonElement { ValueKind: JsonValueKind.Array } element
=> GetParametersFromJsonElement(element),
#endif
_ => ImmutableList.Create()
};
static IReadOnlyList GetParametersFromArray(string?[] array)
{
var parameters = new OpenIddictParameter[array.Length];
for (var index = 0; index < array.Length; index++)
{
parameters[index] = new(array[index]);
}
return parameters;
}
static IReadOnlyList GetParametersFromJsonElement(JsonElement element)
{
var length = element.GetArrayLength();
var parameters = new OpenIddictParameter[length];
for (var index = 0; index < length; index++)
{
parameters[index] = new(element[index]);
}
return parameters;
}
#if SUPPORTS_JSON_NODES
static IReadOnlyList GetParametersFromJsonNode(JsonArray node)
{
var parameters = new OpenIddictParameter[node.Count];
for (var index = 0; index < node.Count; index++)
{
parameters[index] = new(node[index]);
}
return parameters;
}
#endif
}
///
/// Returns the representation of the current instance.
///
/// The representation associated with the parameter value.
public override string? ToString() => Value switch
{
null => string.Empty,
bool value => value ? bool.TrueString : bool.FalseString,
long value => value.ToString(CultureInfo.InvariantCulture),
string value => value,
string?[] value => string.Join(", ", value),
JsonElement value => value.ToString(),
#if SUPPORTS_JSON_NODES
JsonValue value when value.TryGetValue(out JsonElement element)
=> element.ToString(),
JsonValue value when value.TryGetValue(out bool result)
=> result ? bool.TrueString : bool.FalseString,
JsonValue value when value.TryGetValue(out int result)
=> result.ToString(CultureInfo.InvariantCulture),
JsonValue value when value.TryGetValue(out long result)
=> result.ToString(CultureInfo.InvariantCulture),
JsonValue value when value.TryGetValue(out string? result) => result,
JsonNode value => value.ToJsonString(new JsonSerializerOptions
{
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
WriteIndented = false
}),
#endif
_ => string.Empty
};
///
/// Tries to get the child item corresponding to the specified name.
///
/// The name of the child item.
/// An instance containing the item value.
/// if the parameter could be found, otherwise.
public bool TryGetNamedParameter(string name, out OpenIddictParameter value)
{
if (string.IsNullOrEmpty(name))
{
throw new ArgumentException(SR.GetResourceString(SR.ID0192), nameof(name));
}
var result = Value switch
{
// When the parameter is a JsonElement representing an array, return the requested item.
JsonElement { ValueKind: JsonValueKind.Object } element =>
element.TryGetProperty(name, out JsonElement property) ? new(property) : null,
#if SUPPORTS_JSON_NODES
// When the parameter is a JsonObject, return the requested item.
JsonObject node => node.TryGetPropertyValue(name, out JsonNode? property) ? new(property) : null,
// When the parameter is a JsonNode (e.g a JsonValue wrapping a non-primitive type),
// serialize it to a JsonElement first to determine its actual JSON representation
// and apply the same logic as with non-wrapped JsonElement instances.
JsonNode node when JsonSerializer.SerializeToElement(node)
is JsonElement { ValueKind: JsonValueKind.Object } element
=> element.TryGetProperty(name, out JsonElement property) ? new(property) : null,
#endif
_ => (OpenIddictParameter?) null
};
value = result.GetValueOrDefault();
return result.HasValue;
}
///
/// Tries to get the child item corresponding to the specified index.
///
/// The index of the child item.
/// An instance containing the item value.
/// if the parameter could be found, otherwise.
public bool TryGetUnnamedParameter(int index, out OpenIddictParameter value)
{
if (index < 0)
{
throw new ArgumentOutOfRangeException(nameof(index), SR.GetResourceString(SR.ID0193));
}
var result = Value switch
{
// When the parameter is an array of strings, return the requested item.
string?[] array => index < array.Length ? new(array[index]) : null,
// When the parameter is a JsonElement representing an array, return the requested item.
JsonElement { ValueKind: JsonValueKind.Array } element =>
index < element.GetArrayLength() ? new(element[index]) : null,
#if SUPPORTS_JSON_NODES
// When the parameter is a JsonArray, return the requested item.
JsonArray node => index < node.Count ? new(node[index]) : null,
// When the parameter is a JsonNode (e.g a JsonValue wrapping a non-primitive type),
// serialize it to a JsonElement first to determine its actual JSON representation
// and apply the same logic as with non-wrapped JsonElement instances.
JsonNode node when JsonSerializer.SerializeToElement(node)
is JsonElement { ValueKind: JsonValueKind.Array } element
=> index < element.GetArrayLength() ? new(element) : null,
#endif
_ => (OpenIddictParameter?) null
};
value = result.GetValueOrDefault();
return result.HasValue;
}
///
/// Writes the parameter value to the specified JSON writer.
///
/// The UTF-8 JSON writer.
public void WriteTo(Utf8JsonWriter writer!!)
{
switch (Value)
{
// Note: undefined JsonElement values are assimilated to null values.
case null:
case JsonElement { ValueKind: JsonValueKind.Null or JsonValueKind.Undefined }:
writer.WriteNullValue();
break;
case bool value:
writer.WriteBooleanValue(value);
break;
case long value:
writer.WriteNumberValue(value);
break;
case string value:
writer.WriteStringValue(value);
break;
case string?[] value:
writer.WriteStartArray();
for (var index = 0; index < value.Length; index++)
{
writer.WriteStringValue(value[index]);
}
writer.WriteEndArray();
break;
case JsonElement value:
value.WriteTo(writer);
break;
#if SUPPORTS_JSON_NODES
case JsonNode value:
value.WriteTo(writer, new JsonSerializerOptions
{
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
WriteIndented = false
});
break;
#endif
}
}
///
/// Determines whether two instances are equal.
///
/// The first instance.
/// The second instance.
/// if the two instances are equal, otherwise.
public static bool operator ==(OpenIddictParameter left, OpenIddictParameter right) => left.Equals(right);
///
/// Determines whether two instances are not equal.
///
/// The first instance.
/// The second instance.
/// if the two instances are not equal, otherwise.
public static bool operator !=(OpenIddictParameter left, OpenIddictParameter right) => !left.Equals(right);
///
/// Converts an instance to a boolean.
///
/// The parameter to convert.
/// The converted value.
public static explicit operator bool(OpenIddictParameter? parameter)
=> ((bool?) parameter).GetValueOrDefault();
///
/// Converts an instance to a nullable boolean.
///
/// The parameter to convert.
/// The converted value.
public static explicit operator bool?(OpenIddictParameter? parameter)
{
return parameter?.Value switch
{
// When the parameter is a null value or a JsonElement representing null, return null.
null or JsonElement { ValueKind: JsonValueKind.Null or JsonValueKind.Undefined } => null,
// When the parameter is a boolean value, return it as-is.
bool value => value,
// When the parameter is a string value, try to parse it.
string value => bool.TryParse(value, out var result) ? result : null,
// When the parameter is a JsonElement, try to convert it if it's of a supported type.
JsonElement value => ConvertFromJsonElement(value),
#if SUPPORTS_JSON_NODES
// When the parameter is a JsonValue wrapping a JsonElement,
// apply the same logic as with direct JsonElement instances.
JsonValue value when value.TryGetValue(out JsonElement element) => ConvertFromJsonElement(element),
// When the parameter is a JsonValue wrapping a boolean, return it as-is.
JsonValue value when value.TryGetValue(out bool result) => result,
// When the parameter is a JsonValue wrapping a string, try to parse it.
JsonValue value when value.TryGetValue(out string? text) =>
bool.TryParse(text, out var result) ? result : null,
// When the parameter is a JsonNode (e.g a JsonValue wrapping a non-primitive type),
// serialize it to a JsonElement first to determine its actual JSON representation
// and apply the same logic as with non-wrapped JsonElement instances.
JsonNode value when JsonSerializer.SerializeToElement(value) is JsonElement element
=> ConvertFromJsonElement(element),
#endif
// If the parameter is of a different type, return null to indicate the conversion failed.
_ => null
};
static bool? ConvertFromJsonElement(JsonElement element) => element.ValueKind switch
{
// When the parameter is a JsonElement representing a boolean, return it as-is.
JsonValueKind.True => true,
JsonValueKind.False => false,
// When the parameter is a JsonElement representing a string, try to parse it.
JsonValueKind.String => bool.TryParse(element.GetString(), out var result) ? result : null,
_ => null
};
}
///
/// Converts an instance to a .
///
/// The parameter to convert.
/// The converted value.
public static explicit operator JsonElement(OpenIddictParameter? parameter)
{
return parameter?.Value switch
{
// When the parameter is a null value, return an undefined JsonElement.
null => default,
// When the parameter is already a JsonElement, return it as-is.
JsonElement value => value,
#if SUPPORTS_JSON_NODES
// When the parameter is JsonNode, serialize it as a JsonElement.
JsonNode value => JsonSerializer.SerializeToElement(value),
#endif
// When the parameter is a string starting with '{' or '[' (which would correspond
// to a JSON object or array), try to deserialize it to get a JsonElement instance.
string { Length: > 0 } value when value[0] is '{' or '[' =>
DeserializeElement(value) ??
DeserializeElement(JsonSerializer.Serialize(value)) ?? default,
// Otherwise, serialize it to get a JsonElement instance.
#if SUPPORTS_DIRECT_JSON_ELEMENT_SERIALIZATION
object value => JsonSerializer.SerializeToElement(value, value.GetType(), new JsonSerializerOptions
{
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
WriteIndented = false
})
#else
object value => DeserializeElement(JsonSerializer.Serialize(value)) ?? default
#endif
};
static JsonElement? DeserializeElement(string value)
{
try
{
using var document = JsonDocument.Parse(value);
return document.RootElement.Clone();
}
catch (JsonException)
{
return null;
}
}
}
#if SUPPORTS_JSON_NODES
///
/// Converts an instance to a .
///
/// The parameter to convert.
/// The converted value.
public static explicit operator JsonNode?(OpenIddictParameter? parameter)
{
return parameter?.Value switch
{
// When the parameter is a null value or a JsonElement representing null, return null.
null or JsonElement { ValueKind: JsonValueKind.Null or JsonValueKind.Undefined } => null,
// When the parameter is already a JsonNode, return it as-is.
JsonNode value => value,
// When the parameter is a boolean, return a JsonValue.
bool value => JsonValue.Create(value),
// When the parameter is an integer, return a JsonValue.
long value => JsonValue.Create(value),
// When the parameter is a string starting with '{' or '[' (which would correspond
// to a JSON object or array), try to deserialize it to get a JsonNode instance.
string { Length: > 0 } value when value[0] is '{' or '[' => DeserializeNode(value),
// When the parameter is a string, return a JsonValue.
string value => JsonValue.Create(value),
// When the parameter is an array of strings, return a JsonArray.
string?[] value => CreateArray(value),
// When the parameter is JsonElement, deserialize it as a JsonNode.
JsonElement value => JsonSerializer.Deserialize(value),
// If the parameter is of a different type, return null to indicate the conversion failed.
_ => null
};
static JsonNode? DeserializeNode(string value)
{
try
{
return JsonNode.Parse(value);
}
catch (JsonException)
{
return null;
}
}
static JsonArray? CreateArray(string?[] values)
{
var array = new JsonArray();
for (var index = 0; index < values.Length; index++)
{
array.Add(values[index]);
}
return array;
}
}
///
/// Converts an instance to a .
///
/// The parameter to convert.
/// The converted value.
public static explicit operator JsonArray?(OpenIddictParameter? parameter)
=> ((JsonNode?) parameter) as JsonArray;
///
/// Converts an instance to a .
///
/// The parameter to convert.
/// The converted value.
public static explicit operator JsonObject?(OpenIddictParameter? parameter)
=> ((JsonNode?) parameter) as JsonObject;
///
/// Converts an instance to a .
///
/// The parameter to convert.
/// The converted value.
public static explicit operator JsonValue?(OpenIddictParameter? parameter)
=> ((JsonNode?) parameter) as JsonValue;
#endif
///
/// Converts an instance to a long integer.
///
/// The parameter to convert.
/// The converted value.
public static explicit operator long(OpenIddictParameter? parameter)
=> ((long?) parameter).GetValueOrDefault();
///
/// Converts an instance to a nullable long integer.
///
/// The parameter to convert.
/// The converted value.
public static explicit operator long?(OpenIddictParameter? parameter)
{
return parameter?.Value switch
{
// When the parameter is a null value or a JsonElement representing null, return null.
null or JsonElement { ValueKind: JsonValueKind.Null or JsonValueKind.Undefined } => null,
// When the parameter is an integer, return it as-is.
long value => value,
// When the parameter is a string value, try to parse it.
string value => long.TryParse(value, NumberStyles.Integer,
CultureInfo.InvariantCulture, out var result) ? result : null,
// When the parameter is a JsonElement, try to convert it if it's of a supported type.
JsonElement value => ConvertFromJsonElement(value),
#if SUPPORTS_JSON_NODES
// When the parameter is a JsonValue wrapping a JsonElement,
// apply the same logic as with direct JsonElement instances.
JsonValue value when value.TryGetValue(out JsonElement element) => ConvertFromJsonElement(element),
// When the parameter is a JsonValue wrapping an integer, return it as-is.
JsonValue value when value.TryGetValue(out int result) => result,
JsonValue value when value.TryGetValue(out long result) => result,
// When the parameter is a JsonValue wrapping a string, return it as-is.
JsonValue value when value.TryGetValue(out string? text) =>
long.TryParse(text, NumberStyles.Integer,
CultureInfo.InvariantCulture, out var result) ? result : null,
// When the parameter is a JsonNode (e.g a JsonValue wrapping a non-primitive type),
// serialize it to a JsonElement first to determine its actual JSON representation
// and apply the same logic as with non-wrapped JsonElement instances.
JsonNode value when JsonSerializer.SerializeToElement(value) is JsonElement element
=> ConvertFromJsonElement(element),
#endif
// If the parameter is of a different type, return null to indicate the conversion failed.
_ => null
};
static long? ConvertFromJsonElement(JsonElement element) => element.ValueKind switch
{
// When the parameter is a JsonElement representing a number, return it as-is.
JsonValueKind.Number => element.TryGetInt64(out var result) ? result : null,
// When the parameter is a JsonElement representing a string, try to parse it.
JsonValueKind.String => long.TryParse(element.GetString(), NumberStyles.Integer,
CultureInfo.InvariantCulture, out var result) ? result : null,
_ => null
};
}
///
/// Converts an instance to a string.
///
/// The parameter to convert.
/// The converted value.
public static explicit operator string?(OpenIddictParameter? parameter)
{
return parameter?.Value switch
{
// When the parameter is a null value or a JsonElement representing null, return null.
null or JsonElement { ValueKind: JsonValueKind.Null or JsonValueKind.Undefined } => null,
// When the parameter is a string value, return it as-is.
string value => value,
// When the parameter is a boolean value, use its string representation.
bool value => value ? bool.TrueString : bool.FalseString,
// When the parameter is an integer, use its string representation.
long value => value.ToString(CultureInfo.InvariantCulture),
// When the parameter is a JsonElement, try to convert it if it's of a supported type.
JsonElement value => ConvertFromJsonElement(value),
#if SUPPORTS_JSON_NODES
// When the parameter is a JsonValue wrapping a JsonElement,
// apply the same logic as with direct JsonElement instances.
JsonValue value when value.TryGetValue(out JsonElement element) => ConvertFromJsonElement(element),
// When the parameter is a JsonValue wrapping a string, return it as-is.
JsonValue value when value.TryGetValue(out string? result) => result,
// When the parameter is a JsonValue wrapping a boolean, return its representation.
JsonValue value when value.TryGetValue(out bool result) => result ? bool.TrueString : bool.FalseString,
// When the parameter is a JsonValue wrapping a boolean, return its representation.
JsonValue value when value.TryGetValue(out int result) => result.ToString(CultureInfo.InvariantCulture),
JsonValue value when value.TryGetValue(out long result) => result.ToString(CultureInfo.InvariantCulture),
// When the parameter is a JsonNode (e.g a JsonValue wrapping a non-primitive type),
// serialize it to a JsonElement first to determine its actual JSON representation
// and apply the same logic as with non-wrapped JsonElement instances.
JsonNode value when JsonSerializer.SerializeToElement(value) is JsonElement element
=> ConvertFromJsonElement(element),
#endif
// If the parameter is of a different type, return null to indicate the conversion failed.
_ => null
};
static string? ConvertFromJsonElement(JsonElement element) => element.ValueKind switch
{
// When the parameter is a JsonElement representing a string,
// a number or a boolean, return its string representation.
JsonValueKind.String or JsonValueKind.Number or
JsonValueKind.True or JsonValueKind.False
=> element.ToString()!,
_ => null
};
}
///
/// Converts an instance to an array of strings.
///
/// The parameter to convert.
/// The converted value.
public static explicit operator string?[]?(OpenIddictParameter? parameter)
{
return parameter?.Value switch
{
// When the parameter is a null value or a JsonElement representing null, return null.
null or JsonElement { ValueKind: JsonValueKind.Null or JsonValueKind.Undefined } => null,
// When the parameter is already an array of strings, return it as-is.
string?[] value => value,
// When the parameter is a string value, return an array with a single entry.
string value => new string?[] { value },
// When the parameter is a boolean value, return an array with its string representation.
bool value => new string?[] { value ? bool.TrueString : bool.FalseString },
// When the parameter is an integer, return an array with its string representation.
long value => new string?[] { value.ToString(CultureInfo.InvariantCulture) },
// When the parameter is a JsonElement, try to convert it if it's of a supported type.
JsonElement value => ConvertFromJsonElement(value),
#if SUPPORTS_JSON_NODES
// When the parameter is a JsonValue wrapping a JsonElement,
// apply the same logic as with direct JsonElement instances.
JsonValue value when value.TryGetValue(out JsonElement element) => ConvertFromJsonElement(element),
// When the parameter is a JsonValue wrapping a string, return an array with a single entry.
JsonValue value when value.TryGetValue(out string? result) => new string?[] { result },
// When the parameter is a JsonValue wrapping a boolean, return an array with its string representation.
JsonValue value when value.TryGetValue(out bool result)
=> new string?[] { result ? bool.TrueString : bool.FalseString },
// When the parameter is a JsonValue wrapping an integer, return an array with its string representation.
JsonValue value when value.TryGetValue(out int result)
=> new string?[] { result.ToString(CultureInfo.InvariantCulture) },
JsonValue value when value.TryGetValue(out long result)
=> new string?[] { result.ToString(CultureInfo.InvariantCulture) },
// When the parameter is a JsonNode (e.g a JsonValue wrapping a non-primitive type),
// serialize it to a JsonElement first to determine its actual JSON representation
// and apply the same logic as with non-wrapped JsonElement instances.
JsonNode value when JsonSerializer.SerializeToElement(value) is JsonElement element
=> ConvertFromJsonElement(element),
#endif
// If the parameter is of a different type, return null to indicate the conversion failed.
_ => null
};
static string?[]? ConvertFromJsonElement(JsonElement element) => element.ValueKind switch
{
// When the parameter is a JsonElement representing a string, a number
// or a boolean, return an 1-item array with its string representation.
JsonValueKind.String or JsonValueKind.Number or
JsonValueKind.True or JsonValueKind.False
=> new string?[] { element.ToString()! },
// When the parameter is a JsonElement representing an array, return the elements as strings.
JsonValueKind.Array => CreateArrayFromJsonElement(element),
_ => null
};
static string?[]? CreateArrayFromJsonElement(JsonElement element)
{
var length = element.GetArrayLength();
var array = new string?[length];
for (var index = 0; index < length; index++)
{
// Always return a null array if one of the items is a not string, a number or a boolean.
if (element[index] is not { ValueKind: JsonValueKind.String or JsonValueKind.Number or
JsonValueKind.True or JsonValueKind.False } item)
{
return null;
}
array[index] = item.ToString();
}
return array;
}
}
///
/// Converts a boolean to an instance.
///
/// The value to convert
/// An instance.
public static implicit operator OpenIddictParameter(bool value) => new(value);
///
/// Converts a nullable boolean to an instance.
///
/// The value to convert
/// An instance.
public static implicit operator OpenIddictParameter(bool? value) => new(value);
///
/// Converts a to an instance.
///
/// The value to convert
/// An instance.
public static implicit operator OpenIddictParameter(JsonElement value) => new(value);
#if SUPPORTS_JSON_NODES
///
/// Converts a to an instance.
///
/// The value to convert
/// An instance.
public static implicit operator OpenIddictParameter(JsonNode? value) => new(value);
#endif
///
/// Converts a long integer to an instance.
///
/// The value to convert
/// An instance.
public static implicit operator OpenIddictParameter(long value) => new(value);
///
/// Converts a nullable long integer to an instance.
///
/// The value to convert
/// An instance.
public static implicit operator OpenIddictParameter(long? value) => new(value);
///
/// Converts a string to an instance.
///
/// The value to convert
/// An instance.
public static implicit operator OpenIddictParameter(string? value) => new(value);
///
/// Converts an array of strings to an instance.
///
/// The value to convert
/// An instance.
public static implicit operator OpenIddictParameter(string?[]? value) => new(value);
///
/// Determines whether a parameter is null or empty.
///
/// The parameter.
/// if the parameter is null or empty, otherwise.
public static bool IsNullOrEmpty(OpenIddictParameter parameter)
{
return parameter.Value switch
{
null or JsonElement { ValueKind: JsonValueKind.Null or JsonValueKind.Undefined } => true,
string value => value.Length is 0,
string?[] value => value.Length is 0,
JsonElement value => IsEmptyJsonElement(value),
#if SUPPORTS_JSON_NODES
JsonArray value => value.Count is 0,
JsonObject value => value.Count is 0,
JsonValue value when value.TryGetValue(out JsonElement element)
=> IsEmptyJsonElement(element),
JsonValue value when value.TryGetValue(out string? result)
=> string.IsNullOrEmpty(result),
JsonNode value when JsonSerializer.SerializeToElement(value) is JsonElement element
=> IsEmptyJsonElement(element),
#endif
_ => false
};
static bool IsEmptyJsonElement(JsonElement element)
{
switch (element.ValueKind)
{
case JsonValueKind.String:
return string.IsNullOrEmpty(element.GetString());
case JsonValueKind.Array:
return element.GetArrayLength() is 0;
case JsonValueKind.Object:
using (var enumerator = element.EnumerateObject())
{
return !enumerator.MoveNext();
}
default: return false;
}
}
}
}