// 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