// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved. // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. namespace LightJson { using System; using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Globalization; using LightJson.Serialization; /// /// A wrapper object that contains a valid JSON value. /// [DebuggerDisplay("{ToString(),nq}", Type = "JsonValue({Type})")] [DebuggerTypeProxy(typeof(JsonValueDebugView))] internal struct JsonValue { /// /// Represents a null JsonValue. /// public static readonly JsonValue Null = new JsonValue(JsonValueType.Null, default(double), null); private readonly JsonValueType type; private readonly object reference; private readonly double value; /// /// Initializes a new instance of the struct, representing a Boolean value. /// /// The value to be wrapped. public JsonValue(bool? value) { if (value.HasValue) { this.reference = null; this.type = JsonValueType.Boolean; this.value = value.Value ? 1 : 0; } else { this = JsonValue.Null; } } /// /// Initializes a new instance of the struct, representing a Number value. /// /// The value to be wrapped. public JsonValue(double? value) { if (value.HasValue) { this.reference = null; this.type = JsonValueType.Number; this.value = value.Value; } else { this = JsonValue.Null; } } /// /// Initializes a new instance of the struct, representing a String value. /// /// The value to be wrapped. public JsonValue(string value) { if (value != null) { this.value = default(double); this.type = JsonValueType.String; this.reference = value; } else { this = JsonValue.Null; } } /// /// Initializes a new instance of the struct, representing a JsonObject. /// /// The value to be wrapped. public JsonValue(JsonObject value) { if (value != null) { this.value = default(double); this.type = JsonValueType.Object; this.reference = value; } else { this = JsonValue.Null; } } /// /// Initializes a new instance of the struct, representing a Array reference value. /// /// The value to be wrapped. public JsonValue(JsonArray value) { if (value != null) { this.value = default(double); this.type = JsonValueType.Array; this.reference = value; } else { this = JsonValue.Null; } } /// /// Initializes a new instance of the struct. /// /// The Json type of the JsonValue. /// /// The internal value of the JsonValue. /// This is used when the Json type is Number or Boolean. /// /// /// The internal value reference of the JsonValue. /// This value is used when the Json type is String, JsonObject, or JsonArray. /// private JsonValue(JsonValueType type, double value, object reference) { this.type = type; this.value = value; this.reference = reference; } /// /// Gets the type of this JsonValue. /// /// The type of this JsonValue. public JsonValueType Type { get { return this.type; } } /// /// Gets a value indicating whether this JsonValue is Null. /// /// A value indicating whether this JsonValue is Null. public bool IsNull { get { return this.Type == JsonValueType.Null; } } /// /// Gets a value indicating whether this JsonValue is a Boolean. /// /// A value indicating whether this JsonValue is a Boolean. public bool IsBoolean { get { return this.Type == JsonValueType.Boolean; } } /// /// Gets a value indicating whether this JsonValue is an Integer. /// /// A value indicating whether this JsonValue is an Integer. public bool IsInteger { get { if (!this.IsNumber) { return false; } var value = this.value; return unchecked((int)value) == value; } } /// /// Gets a value indicating whether this JsonValue is a Number. /// /// A value indicating whether this JsonValue is a Number. public bool IsNumber { get { return this.Type == JsonValueType.Number; } } /// /// Gets a value indicating whether this JsonValue is a String. /// /// A value indicating whether this JsonValue is a String. public bool IsString { get { return this.Type == JsonValueType.String; } } /// /// Gets a value indicating whether this JsonValue is a JsonObject. /// /// A value indicating whether this JsonValue is a JsonObject. public bool IsJsonObject { get { return this.Type == JsonValueType.Object; } } /// /// Gets a value indicating whether this JsonValue is a JsonArray. /// /// A value indicating whether this JsonValue is a JsonArray. public bool IsJsonArray { get { return this.Type == JsonValueType.Array; } } /// /// Gets a value indicating whether this JsonValue represents a DateTime. /// /// A value indicating whether this JsonValue represents a DateTime. public bool IsDateTime { get { return this.AsDateTime != null; } } /// /// Gets a value indicating whether this value is true or false. /// /// This value as a Boolean type. public bool AsBoolean { get { switch (this.Type) { case JsonValueType.Boolean: return this.value == 1; case JsonValueType.Number: return this.value != 0; case JsonValueType.String: return (string)this.reference != string.Empty; case JsonValueType.Object: case JsonValueType.Array: return true; default: return false; } } } /// /// Gets this value as an Integer type. /// /// This value as an Integer type. public int AsInteger { get { var value = this.AsNumber; // Prevent overflow if the value doesn't fit. if (value >= int.MaxValue) { return int.MaxValue; } if (value <= int.MinValue) { return int.MinValue; } return (int)value; } } /// /// Gets this value as a Number type. /// /// This value as a Number type. public double AsNumber { get { switch (this.Type) { case JsonValueType.Boolean: return (this.value == 1) ? 1 : 0; case JsonValueType.Number: return this.value; case JsonValueType.String: double number; if (double.TryParse((string)this.reference, NumberStyles.Float, CultureInfo.InvariantCulture, out number)) { return number; } else { goto default; } default: return 0; } } } /// /// Gets this value as a String type. /// /// This value as a String type. public string? AsString { get { switch (this.Type) { case JsonValueType.Boolean: return (this.value == 1) ? "true" : "false"; case JsonValueType.Number: return this.value.ToString(CultureInfo.InvariantCulture); case JsonValueType.String: return (string)this.reference; default: return null; } } } /// /// Gets this value as an JsonObject. /// /// This value as an JsonObject. public JsonObject? AsJsonObject { get { return this.IsJsonObject ? (JsonObject)this.reference : null; } } /// /// Gets this value as an JsonArray. /// /// This value as an JsonArray. public JsonArray? AsJsonArray { get { return this.IsJsonArray ? (JsonArray)this.reference : null; } } /// /// Gets this value as a system.DateTime. /// /// This value as a system.DateTime. public DateTime? AsDateTime { get { DateTime value; if (this.IsString && DateTime.TryParse((string)this.reference, out value)) { return value; } else { return null; } } } /// /// Gets this (inner) value as a System.object. /// /// This (inner) value as a System.object. public object? AsObject { get { switch (this.Type) { case JsonValueType.Boolean: case JsonValueType.Number: return this.value; case JsonValueType.String: case JsonValueType.Object: case JsonValueType.Array: return this.reference; default: return null; } } } /// /// Gets or sets the value associated with the specified key. /// /// The key of the value to get or set. /// /// Thrown when this JsonValue is not a JsonObject. /// public JsonValue this[string key] { get { if (this.IsJsonObject) { return ((JsonObject)this.reference)[key]; } else { throw new InvalidOperationException("This value does not represent a JsonObject."); } } set { if (this.IsJsonObject) { ((JsonObject)this.reference)[key] = value; } else { throw new InvalidOperationException("This value does not represent a JsonObject."); } } } /// /// Gets or sets the value at the specified index. /// /// The zero-based index of the value to get or set. /// /// Thrown when this JsonValue is not a JsonArray /// public JsonValue this[int index] { get { if (this.IsJsonArray) { return ((JsonArray)this.reference)[index]; } else { throw new InvalidOperationException("This value does not represent a JsonArray."); } } set { if (this.IsJsonArray) { ((JsonArray)this.reference)[index] = value; } else { throw new InvalidOperationException("This value does not represent a JsonArray."); } } } /// /// Converts the given nullable boolean into a JsonValue. /// /// The value to be converted. public static implicit operator JsonValue(bool? value) { return new JsonValue(value); } /// /// Converts the given nullable double into a JsonValue. /// /// The value to be converted. public static implicit operator JsonValue(double? value) { return new JsonValue(value); } /// /// Converts the given string into a JsonValue. /// /// The value to be converted. public static implicit operator JsonValue(string value) { return new JsonValue(value); } /// /// Converts the given JsonObject into a JsonValue. /// /// The value to be converted. public static implicit operator JsonValue(JsonObject value) { return new JsonValue(value); } /// /// Converts the given JsonArray into a JsonValue. /// /// The value to be converted. public static implicit operator JsonValue(JsonArray value) { return new JsonValue(value); } /// /// Converts the given DateTime? into a JsonValue. /// /// /// The DateTime value will be stored as a string using ISO 8601 format, /// since JSON does not define a DateTime type. /// /// The value to be converted. public static implicit operator JsonValue(DateTime? value) { if (value == null) { return JsonValue.Null; } return new JsonValue(value.Value.ToString("o")); } /// /// Converts the given JsonValue into an Int. /// /// The JsonValue to be converted. public static explicit operator int(JsonValue jsonValue) { if (jsonValue.IsInteger) { return jsonValue.AsInteger; } else { return 0; } } /// /// Converts the given JsonValue into a nullable Int. /// /// The JsonValue to be converted. /// /// Throws System.InvalidCastException when the inner value type of the /// JsonValue is not the desired type of the conversion. /// public static explicit operator int?(JsonValue jsonValue) { if (jsonValue.IsNull) { return null; } else { return (int)jsonValue; } } /// /// Converts the given JsonValue into a Bool. /// /// The JsonValue to be converted. public static explicit operator bool(JsonValue jsonValue) { if (jsonValue.IsBoolean) { return jsonValue.value == 1; } else { return false; } } /// /// Converts the given JsonValue into a nullable Bool. /// /// The JsonValue to be converted. /// /// Throws System.InvalidCastException when the inner value type of the /// JsonValue is not the desired type of the conversion. /// public static explicit operator bool?(JsonValue jsonValue) { if (jsonValue.IsNull) { return null; } else { return (bool)jsonValue; } } /// /// Converts the given JsonValue into a Double. /// /// The JsonValue to be converted. public static explicit operator double(JsonValue jsonValue) { if (jsonValue.IsNumber) { return jsonValue.value; } else { return double.NaN; } } /// /// Converts the given JsonValue into a nullable Double. /// /// The JsonValue to be converted. /// /// Throws System.InvalidCastException when the inner value type of the /// JsonValue is not the desired type of the conversion. /// public static explicit operator double?(JsonValue jsonValue) { if (jsonValue.IsNull) { return null; } else { return (double)jsonValue; } } /// /// Converts the given JsonValue into a String. /// /// The JsonValue to be converted. public static explicit operator string(JsonValue jsonValue) { if (jsonValue.IsString || jsonValue.IsNull) { return jsonValue.reference as string; } else { return null; } } /// /// Converts the given JsonValue into a JsonObject. /// /// The JsonValue to be converted. public static explicit operator JsonObject(JsonValue jsonValue) { if (jsonValue.IsJsonObject || jsonValue.IsNull) { return jsonValue.reference as JsonObject; } else { return null; } } /// /// Converts the given JsonValue into a JsonArray. /// /// The JsonValue to be converted. public static explicit operator JsonArray(JsonValue jsonValue) { if (jsonValue.IsJsonArray || jsonValue.IsNull) { return jsonValue.reference as JsonArray; } else { return null; } } /// /// Converts the given JsonValue into a DateTime. /// /// The JsonValue to be converted. public static explicit operator DateTime(JsonValue jsonValue) { var dateTime = jsonValue.AsDateTime; if (dateTime.HasValue) { return dateTime.Value; } else { return DateTime.MinValue; } } /// /// Converts the given JsonValue into a nullable DateTime. /// /// The JsonValue to be converted. public static explicit operator DateTime?(JsonValue jsonValue) { if (jsonValue.IsDateTime || jsonValue.IsNull) { return jsonValue.AsDateTime; } else { return null; } } /// /// Returns a value indicating whether the two given JsonValues are equal. /// /// First JsonValue to compare. /// Second JsonValue to compare. public static bool operator ==(JsonValue a, JsonValue b) { return (a.Type == b.Type) && (a.value == b.value) && Equals(a.reference, b.reference); } /// /// Returns a value indicating whether the two given JsonValues are unequal. /// /// First JsonValue to compare. /// Second JsonValue to compare. public static bool operator !=(JsonValue a, JsonValue b) { return !(a == b); } /// /// Returns a JsonValue by parsing the given string. /// /// The JSON-formatted string to be parsed. /// The representing the parsed text. public static JsonValue Parse(string text) { return JsonReader.Parse(text); } /// public override bool Equals(object? obj) { if (obj == null) { return this.IsNull; } var jsonValue = obj as JsonValue?; if (jsonValue == null) { return false; } else { return this == jsonValue.Value; } } /// public override int GetHashCode() { if (this.IsNull) { return this.Type.GetHashCode(); } else { return this.Type.GetHashCode() ^ this.value.GetHashCode() ^ EqualityComparer.Default.GetHashCode(this.reference); } } [ExcludeFromCodeCoverage] private class JsonValueDebugView { private JsonValue jsonValue; public JsonValueDebugView(JsonValue jsonValue) { this.jsonValue = jsonValue; } [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)] public JsonObject? ObjectView { get { if (this.jsonValue.IsJsonObject) { return (JsonObject)this.jsonValue.reference; } else { return null; } } } [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)] public JsonArray? ArrayView { get { if (this.jsonValue.IsJsonArray) { return (JsonArray)this.jsonValue.reference; } else { return null; } } } public JsonValueType Type { get { return this.jsonValue.Type; } } public object Value { get { if (this.jsonValue.IsJsonObject) { return (JsonObject)this.jsonValue.reference; } else if (this.jsonValue.IsJsonArray) { return (JsonArray)this.jsonValue.reference; } else { return this.jsonValue; } } } } } }