| File: ApiLifecycle\Json\JsonValue.cs | Web Access |
| Project: src\src\Analyzers\Microsoft.Analyzers.Local\Microsoft.Analyzers.Local.csproj (Microsoft.Analyzers.Local) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // Forked from StyleCop.Analyzers repo. using System; using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Globalization; namespace Microsoft.Extensions.LocalAnalyzers.Json; /// <summary> /// A wrapper object that contains a valid JSON value. /// </summary> [DebuggerDisplay("{ToString(),nq}", Type = "JsonValue({Type})")] [DebuggerTypeProxy(typeof(JsonValueDebugView))] [SuppressMessage("Major Bug", "S1244:Floating point numbers should not be tested for equality", Justification = "Would require unnecessary refactor.")] internal readonly struct JsonValue : IEquatable<JsonValue> { /// <summary> /// Represents a <see langword="null" /> JsonValue. /// </summary> public static readonly JsonValue Null = new(JsonValueType.Null, default, null); private readonly object? _reference; private readonly double _value; /// <summary> /// Initializes a new instance of the <see cref="JsonValue"/> struct, representing a Boolean value. /// </summary> /// <param name="value">The value to be wrapped.</param> public JsonValue(bool? value) { if (!value.HasValue) { this = Null; return; } Type = JsonValueType.Boolean; _reference = null; _value = value.Value ? 1 : 0; } /// <summary> /// Initializes a new instance of the <see cref="JsonValue"/> struct, representing a Number value. /// </summary> /// <param name="value">The value to be wrapped.</param> public JsonValue(double? value) { if (!value.HasValue) { this = Null; return; } Type = JsonValueType.Number; _reference = null; _value = value.Value; } /// <summary> /// Initializes a new instance of the <see cref="JsonValue"/> struct, representing a String value. /// </summary> /// <param name="value">The value to be wrapped.</param> public JsonValue(string? value) { if (value is null) { this = Null; return; } Type = JsonValueType.String; _value = default; _reference = value; } /// <summary> /// Initializes a new instance of the <see cref="JsonValue"/> struct, representing a JsonObject. /// </summary> /// <param name="value">The value to be wrapped.</param> public JsonValue(JsonObject? value) { if (value is null) { this = Null; return; } Type = JsonValueType.Object; _value = default; _reference = value; } /// <summary> /// Initializes a new instance of the <see cref="JsonValue"/> struct, representing a Array reference value. /// </summary> /// <param name="value">The value to be wrapped.</param> public JsonValue(JsonArray? value) { if (value is null) { this = Null; return; } Type = JsonValueType.Array; _value = default; _reference = value; } /// <summary> /// Initializes a new instance of the <see cref="JsonValue"/> struct. /// </summary> /// <param name="type">The Json type of the JsonValue.</param> /// <param name="value"> /// The internal value of the JsonValue. /// This is used when the Json type is Number or Boolean. /// </param> /// <param name="reference"> /// The internal value reference of the JsonValue. /// This value is used when the Json type is String, JsonObject, or JsonArray. /// </param> private JsonValue(JsonValueType type, double value, object? reference) { Type = type; _value = value; _reference = reference; } /// <summary> /// Gets the type of this JsonValue. /// </summary> /// <value>The type of this JsonValue.</value> public JsonValueType Type { get; } /// <summary> /// Gets a value indicating whether this JsonValue is Null. /// </summary> /// <value>A value indicating whether this JsonValue is Null.</value> public bool IsNull => Type == JsonValueType.Null; /// <summary> /// Gets a value indicating whether this JsonValue is a Boolean. /// </summary> /// <value>A value indicating whether this JsonValue is a Boolean.</value> public bool IsBoolean => Type == JsonValueType.Boolean; /// <summary> /// Gets a value indicating whether this JsonValue is an Integer. /// </summary> /// <value>A value indicating whether this JsonValue is an Integer.</value> #pragma warning disable S2589 // Boolean expressions should not be gratuitous public bool IsInteger => IsNumber && unchecked((int)_value) == _value; #pragma warning restore S2589 // Boolean expressions should not be gratuitous /// <summary> /// Gets a value indicating whether this JsonValue is a Number. /// </summary> /// <value>A value indicating whether this JsonValue is a Number.</value> public bool IsNumber => Type == JsonValueType.Number; /// <summary> /// Gets a value indicating whether this JsonValue is a String. /// </summary> /// <value>A value indicating whether this JsonValue is a String.</value> public bool IsString => Type == JsonValueType.String; /// <summary> /// Gets a value indicating whether this JsonValue is a JsonObject. /// </summary> /// <value>A value indicating whether this JsonValue is a JsonObject.</value> public bool IsJsonObject => Type == JsonValueType.Object; /// <summary> /// Gets a value indicating whether this JsonValue is a JsonArray. /// </summary> /// <value>A value indicating whether this JsonValue is a JsonArray.</value> public bool IsJsonArray => Type == JsonValueType.Array; /// <summary> /// Gets a value indicating whether this JsonValue represents a DateTime. /// </summary> /// <value>A value indicating whether this JsonValue represents a DateTime.</value> public bool IsDateTime => AsDateTime != null; /// <summary> /// Gets a value indicating whether this value is true or false. /// </summary> /// <value>This value as a Boolean type.</value> public bool AsBoolean => Type switch { JsonValueType.Boolean => _value == 1, JsonValueType.Number => _value != 0, JsonValueType.String => !string.IsNullOrEmpty((string?)_reference), JsonValueType.Object or JsonValueType.Array => true, _ => false, }; /// <summary> /// Gets this value as an Integer type. /// </summary> /// <value>This value as an Integer type.</value> public int AsInteger { get { var current = AsNumber; if (current >= int.MaxValue) { return int.MaxValue; } if (_value <= int.MinValue) { return int.MinValue; } return (int)_value; } } /// <summary> /// Gets this value as a Number type. /// </summary> /// <value>This value as a Number type.</value> public double AsNumber => Type switch { JsonValueType.Boolean => _value == 1 ? 1 : 0, JsonValueType.Number => _value, JsonValueType.String => double.TryParse((string?)_reference, NumberStyles.Float, CultureInfo.InvariantCulture, out var number) ? number : 0, _ => 0 }; /// <summary> /// Gets this value as a String type. /// </summary> /// <value>This value as a String type.</value> public string? AsString => Type switch { JsonValueType.Boolean => (_value == 1) ? "true" : "false", JsonValueType.Number => _value.ToString(CultureInfo.InvariantCulture), JsonValueType.String => (string?)_reference, _ => null, }; /// <summary> /// Gets this value as an JsonObject. /// </summary> /// <value>This value as an JsonObject.</value> #pragma warning disable S1168 // Empty arrays and collections should be returned instead of null public JsonObject? AsJsonObject => IsJsonObject ? (JsonObject?)_reference : null; /// <summary> /// Gets this value as an JsonArray. /// </summary> /// <value>This value as an JsonArray.</value> public JsonArray? AsJsonArray => IsJsonArray ? (JsonArray?)_reference : null; #pragma warning restore S1168 // Empty arrays and collections should be returned instead of null /// <summary> /// Gets this value as a system.DateTime. /// </summary> /// <value>This value as a system.DateTime.</value> public DateTime? AsDateTime => IsString && DateTime.TryParse((string?)_reference ?? string.Empty, DateTimeFormatInfo.InvariantInfo, DateTimeStyles.None, out var value) ? value : null; /// <summary> /// Gets this (inner) value as a System.object. /// </summary> /// <value>This (inner) value as a System.object.</value> public object? AsObject => Type switch { JsonValueType.Boolean or JsonValueType.Number => _value, JsonValueType.String or JsonValueType.Object or JsonValueType.Array => _reference, _ => null }; /// <summary> /// Gets or sets the value associated with the specified key. /// </summary> /// <param name="key">The key of the value to get or set.</param> /// <exception cref="System.InvalidOperationException"> /// Thrown when this JsonValue is not a JsonObject. /// </exception> public JsonValue this[string key] { get { if (IsJsonObject) { return ((JsonObject)_reference!)[key]; } else { throw new InvalidOperationException("This value does not represent a JsonObject."); } } set { if (IsJsonObject) { ((JsonObject)_reference!)[key] = value; } else { throw new InvalidOperationException("This value does not represent a JsonObject."); } } } /// <summary> /// Gets or sets the value at the specified index. /// </summary> /// <param name="index">The zero-based index of the value to get or set.</param> /// <exception cref="InvalidOperationException"> /// Thrown when this <see cref="JsonValue"/> is not a <see cref="JsonArray"/>. /// </exception> public JsonValue this[int index] { get { if (IsJsonArray) { return ((JsonArray)_reference!)[index]; } else { throw new InvalidOperationException("This value does not represent a JsonArray."); } } set { if (IsJsonArray) { ((JsonArray)_reference!)[index] = value; } else { throw new InvalidOperationException("This value does not represent a JsonArray."); } } } /// <summary> /// Converts the given nullable boolean into a JsonValue. /// </summary> /// <param name="value">The value to be converted.</param> public static implicit operator JsonValue(bool? value) { return new JsonValue(value); } /// <summary> /// Converts the given nullable double into a JsonValue. /// </summary> /// <param name="value">The value to be converted.</param> public static implicit operator JsonValue(double? value) { return new JsonValue(value); } /// <summary> /// Converts the given string into a JsonValue. /// </summary> /// <param name="value">The value to be converted.</param> public static implicit operator JsonValue(string value) { return new JsonValue(value); } /// <summary> /// Converts the given JsonObject into a JsonValue. /// </summary> /// <param name="value">The value to be converted.</param> public static implicit operator JsonValue(JsonObject value) { return new JsonValue(value); } /// <summary> /// Converts the given JsonArray into a JsonValue. /// </summary> /// <param name="value">The value to be converted.</param> public static implicit operator JsonValue(JsonArray value) { return new JsonValue(value); } /// <summary> /// Converts the given DateTime? into a JsonValue. /// </summary> /// <remarks> /// <para>The DateTime value will be stored as a string using ISO 8601 format, /// since JSON does not define a DateTime type.</para> /// </remarks> /// <param name="value">The value to be converted.</param> public static implicit operator JsonValue(DateTime? value) { return value == null ? Null : new JsonValue(value.Value.ToString("o", CultureInfo.InvariantCulture)); } /// <summary> /// Converts the given JsonValue into an Int. /// </summary> /// <param name="jsonValue">The JsonValue to be converted.</param> public static explicit operator int(JsonValue jsonValue) { return jsonValue.IsInteger ? jsonValue.AsInteger : 0; } /// <summary> /// Converts the given JsonValue into a nullable Int. /// </summary> /// <param name="jsonValue">The JsonValue to be converted.</param> /// <exception cref="System.InvalidCastException"> /// Throws System.InvalidCastException when the inner value type of the /// JsonValue is not the desired type of the conversion. /// </exception> public static explicit operator int?(JsonValue jsonValue) { return jsonValue.IsNull ? null : (int)jsonValue; } /// <summary> /// Converts the given JsonValue into a Bool. /// </summary> /// <param name="jsonValue">The JsonValue to be converted.</param> public static explicit operator bool(JsonValue jsonValue) { return jsonValue.IsBoolean && jsonValue._value == 1; } /// <summary> /// Converts the given JsonValue into a nullable Bool. /// </summary> /// <param name="jsonValue">The JsonValue to be converted.</param> /// <exception cref="System.InvalidCastException"> /// Throws System.InvalidCastException when the inner value type of the /// JsonValue is not the desired type of the conversion. /// </exception> public static explicit operator bool?(JsonValue jsonValue) { return jsonValue.IsNull ? null : (bool)jsonValue; } /// <summary> /// Converts the given JsonValue into a Double. /// </summary> /// <param name="jsonValue">The JsonValue to be converted.</param> public static explicit operator double(JsonValue jsonValue) { return jsonValue.IsNumber ? jsonValue._value : double.NaN; } /// <summary> /// Converts the given JsonValue into a nullable Double. /// </summary> /// <param name="jsonValue">The JsonValue to be converted.</param> /// <exception cref="System.InvalidCastException"> /// Throws System.InvalidCastException when the inner value type of the /// JsonValue is not the desired type of the conversion. /// </exception> public static explicit operator double?(JsonValue jsonValue) { return jsonValue.IsNull ? null : (double)jsonValue; } /// <summary> /// Converts the given JsonValue into a String. /// </summary> /// <param name="jsonValue">The JsonValue to be converted.</param> public static explicit operator string?(JsonValue jsonValue) { return jsonValue.IsString || jsonValue.IsNull ? jsonValue._reference as string : null; } /// <summary> /// Converts the given JsonValue into a JsonObject. /// </summary> /// <param name="jsonValue">The JsonValue to be converted.</param> public static explicit operator JsonObject?(JsonValue jsonValue) { return jsonValue.IsJsonObject || jsonValue.IsNull ? jsonValue._reference as JsonObject : null; } /// <summary> /// Converts the given JsonValue into a JsonArray. /// </summary> /// <param name="jsonValue">The JsonValue to be converted.</param> public static explicit operator JsonArray?(JsonValue jsonValue) { return jsonValue.IsJsonArray || jsonValue.IsNull ? jsonValue._reference as JsonArray : null; } /// <summary> /// Converts the given JsonValue into a DateTime. /// </summary> /// <param name="jsonValue">The JsonValue to be converted.</param> public static explicit operator DateTime(JsonValue jsonValue) { return jsonValue.AsDateTime ?? DateTime.MinValue; } /// <summary> /// Converts the given JsonValue into a nullable DateTime. /// </summary> /// <param name="jsonValue">The JsonValue to be converted.</param> public static explicit operator DateTime?(JsonValue jsonValue) { return jsonValue.IsDateTime || jsonValue.IsNull ? jsonValue.AsDateTime : null; } /// <summary> /// Returns a value indicating whether the two given JsonValues are equal. /// </summary> /// <param name="a">First JsonValue to compare.</param> /// <param name="b">Second JsonValue to compare.</param> public static bool operator ==(JsonValue a, JsonValue b) { return (a.Type == b.Type) && (a._value == b._value) && Equals(a._reference, b._reference); } /// <summary> /// Returns a value indicating whether the two given JsonValues are unequal. /// </summary> /// <param name="a">First JsonValue to compare.</param> /// <param name="b">Second JsonValue to compare.</param> public static bool operator !=(JsonValue a, JsonValue b) { return !(a == b); } /// <summary> /// Returns a JsonValue by parsing the given string. /// </summary> /// <param name="text">The JSON-formatted string to be parsed.</param> /// <returns>The <see cref="JsonValue"/> representing the parsed text.</returns> public static JsonValue Parse(string text) { return JsonReader.Parse(text); } public bool Equals(JsonValue other) { return other == this; } /// <inheritdoc/> public override bool Equals(object? obj) { if (obj is JsonValue jv) { return this == jv; } return IsNull && obj is null; } /// <inheritdoc/> public override int GetHashCode() { var r = _reference != null ? EqualityComparer<object>.Default.GetHashCode(_reference) : 1; return IsNull ? Type.GetHashCode() : Type.GetHashCode() ^ _value.GetHashCode() ^ r; } [ExcludeFromCodeCoverage] private sealed class JsonValueDebugView { private readonly JsonValue _jsonValue; [SuppressMessage("Major Code Smell", "S1144:Unused private types or members should be removed", Justification = "Used by debugger.")] public JsonValueDebugView(JsonValue jsonValue) { _jsonValue = jsonValue; } [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)] #pragma warning disable S1168 // Empty arrays and collections should be returned instead of null public JsonObject? ObjectView => _jsonValue.IsJsonObject ? (JsonObject?)_jsonValue._reference : null; [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)] public JsonArray? ArrayView => _jsonValue.IsJsonArray ? (JsonArray?)_jsonValue._reference : null; #pragma warning restore S1168 // Empty arrays and collections should be returned instead of null public JsonValueType Type => _jsonValue.Type; public object? Value => _jsonValue.IsJsonObject || _jsonValue.IsJsonArray ? _jsonValue._reference : null; } }