920 lines
31 KiB
C#
920 lines
31 KiB
C#
|
using System;
|
||
|
using System.Collections.Generic;
|
||
|
using System.Globalization;
|
||
|
using System.Linq;
|
||
|
using System.Text;
|
||
|
using System.Text.RegularExpressions;
|
||
|
|
||
|
namespace UnityEngine.InputSystem.Utilities
|
||
|
{
|
||
|
/// <summary>
|
||
|
/// A JSON parser that instead of turning a string in JSON format into a
|
||
|
/// C# object graph, allows navigating the source text directly.
|
||
|
/// </summary>
|
||
|
/// <remarks>
|
||
|
/// This helper is most useful for avoiding a great many string and general object allocations
|
||
|
/// that would happen when turning a JSON object into a C# object graph.
|
||
|
/// </remarks>
|
||
|
internal struct JsonParser
|
||
|
{
|
||
|
public JsonParser(string json)
|
||
|
: this()
|
||
|
{
|
||
|
if (json == null)
|
||
|
throw new ArgumentNullException(nameof(json));
|
||
|
|
||
|
m_Text = json;
|
||
|
m_Length = json.Length;
|
||
|
}
|
||
|
|
||
|
public void Reset()
|
||
|
{
|
||
|
m_Position = 0;
|
||
|
m_MatchAnyElementInArray = false;
|
||
|
m_DryRun = false;
|
||
|
}
|
||
|
|
||
|
public override string ToString()
|
||
|
{
|
||
|
if (m_Text != null)
|
||
|
return $"{m_Position}: {m_Text.Substring(m_Position)}";
|
||
|
return base.ToString();
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Navigate to the given property.
|
||
|
/// </summary>
|
||
|
/// <param name="path"></param>
|
||
|
/// <remarks>
|
||
|
/// This navigates from the current property.
|
||
|
/// </remarks>
|
||
|
public bool NavigateToProperty(string path)
|
||
|
{
|
||
|
if (string.IsNullOrEmpty(path))
|
||
|
throw new ArgumentNullException(nameof(path));
|
||
|
|
||
|
var pathLength = path.Length;
|
||
|
var pathPosition = 0;
|
||
|
|
||
|
m_DryRun = true;
|
||
|
if (!ParseToken('{'))
|
||
|
return false;
|
||
|
|
||
|
while (m_Position < m_Length && pathPosition < pathLength)
|
||
|
{
|
||
|
// Find start of property name.
|
||
|
SkipWhitespace();
|
||
|
if (m_Position == m_Length)
|
||
|
return false;
|
||
|
if (m_Text[m_Position] != '"')
|
||
|
return false;
|
||
|
++m_Position;
|
||
|
|
||
|
// Try to match single path component.
|
||
|
var pathStartPosition = pathPosition;
|
||
|
while (pathPosition < pathLength)
|
||
|
{
|
||
|
var ch = path[pathPosition];
|
||
|
if (ch == '/' || ch == '[')
|
||
|
break;
|
||
|
|
||
|
if (m_Text[m_Position] != ch)
|
||
|
break;
|
||
|
|
||
|
++m_Position;
|
||
|
++pathPosition;
|
||
|
}
|
||
|
|
||
|
// See if we have a match.
|
||
|
if (m_Position < m_Length && m_Text[m_Position] == '"' && (pathPosition >= pathLength || path[pathPosition] == '/' || path[pathPosition] == '['))
|
||
|
{
|
||
|
// Have matched a property name. Navigate to value.
|
||
|
++m_Position;
|
||
|
if (!SkipToValue())
|
||
|
return false;
|
||
|
|
||
|
// Check if we have matched everything in the path.
|
||
|
if (pathPosition >= pathLength)
|
||
|
return true;
|
||
|
if (path[pathPosition] == '/')
|
||
|
{
|
||
|
++pathPosition;
|
||
|
if (!ParseToken('{'))
|
||
|
return false;
|
||
|
}
|
||
|
else if (path[pathPosition] == '[')
|
||
|
{
|
||
|
++pathPosition;
|
||
|
if (pathPosition == pathLength)
|
||
|
throw new ArgumentException("Malformed JSON property path: " + path, nameof(path));
|
||
|
if (path[pathPosition] == ']')
|
||
|
{
|
||
|
m_MatchAnyElementInArray = true;
|
||
|
++pathPosition;
|
||
|
if (pathPosition == pathLength)
|
||
|
return true;
|
||
|
}
|
||
|
else
|
||
|
throw new NotImplementedException("Navigating to specific array element");
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// This property isn't it. Skip the property and its value and reset
|
||
|
// to where we started in this iteration in the property path.
|
||
|
|
||
|
pathPosition = pathStartPosition;
|
||
|
while (m_Position < m_Length && m_Text[m_Position] != '"')
|
||
|
++m_Position;
|
||
|
if (m_Position == m_Length || m_Text[m_Position] != '"')
|
||
|
return false;
|
||
|
++m_Position;
|
||
|
if (!SkipToValue() || !ParseValue())
|
||
|
return false;
|
||
|
SkipWhitespace();
|
||
|
if (m_Position == m_Length || m_Text[m_Position] == '}' || m_Text[m_Position] != ',')
|
||
|
return false;
|
||
|
++m_Position;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Return true if the current property has a value matching <paramref name="expectedValue"/>.
|
||
|
/// </summary>
|
||
|
/// <param name="expectedValue"></param>
|
||
|
/// <returns></returns>
|
||
|
public bool CurrentPropertyHasValueEqualTo(JsonValue expectedValue)
|
||
|
{
|
||
|
// Grab property value.
|
||
|
var savedPosition = m_Position;
|
||
|
m_DryRun = false;
|
||
|
if (!ParseValue(out var propertyValue))
|
||
|
{
|
||
|
m_Position = savedPosition;
|
||
|
return false;
|
||
|
}
|
||
|
m_Position = savedPosition;
|
||
|
|
||
|
// Match given value.
|
||
|
var isMatch = false;
|
||
|
if (propertyValue.type == JsonValueType.Array && m_MatchAnyElementInArray)
|
||
|
{
|
||
|
var array = propertyValue.arrayValue;
|
||
|
for (var i = 0; !isMatch && i < array.Count; ++i)
|
||
|
isMatch = array[i] == expectedValue;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
isMatch = propertyValue == expectedValue;
|
||
|
}
|
||
|
|
||
|
return isMatch;
|
||
|
}
|
||
|
|
||
|
public bool ParseToken(char token)
|
||
|
{
|
||
|
SkipWhitespace();
|
||
|
if (m_Position == m_Length)
|
||
|
return false;
|
||
|
|
||
|
if (m_Text[m_Position] != token)
|
||
|
return false;
|
||
|
|
||
|
++m_Position;
|
||
|
SkipWhitespace();
|
||
|
|
||
|
return m_Position < m_Length;
|
||
|
}
|
||
|
|
||
|
public bool ParseValue()
|
||
|
{
|
||
|
return ParseValue(out var result);
|
||
|
}
|
||
|
|
||
|
public bool ParseValue(out JsonValue result)
|
||
|
{
|
||
|
result = default;
|
||
|
|
||
|
SkipWhitespace();
|
||
|
if (m_Position == m_Length)
|
||
|
return false;
|
||
|
|
||
|
var ch = m_Text[m_Position];
|
||
|
switch (ch)
|
||
|
{
|
||
|
case '"':
|
||
|
if (ParseStringValue(out result))
|
||
|
return true;
|
||
|
break;
|
||
|
case '[':
|
||
|
if (ParseArrayValue(out result))
|
||
|
return true;
|
||
|
break;
|
||
|
case '{':
|
||
|
if (ParseObjectValue(out result))
|
||
|
return true;
|
||
|
break;
|
||
|
case 't':
|
||
|
case 'f':
|
||
|
if (ParseBooleanValue(out result))
|
||
|
return true;
|
||
|
break;
|
||
|
case 'n':
|
||
|
if (ParseNullValue(out result))
|
||
|
return true;
|
||
|
break;
|
||
|
default:
|
||
|
if (ParseNumber(out result))
|
||
|
return true;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
public bool ParseStringValue(out JsonValue result)
|
||
|
{
|
||
|
result = default;
|
||
|
|
||
|
SkipWhitespace();
|
||
|
if (m_Position == m_Length || m_Text[m_Position] != '"')
|
||
|
return false;
|
||
|
++m_Position;
|
||
|
|
||
|
var startIndex = m_Position;
|
||
|
var hasEscapes = false;
|
||
|
|
||
|
while (m_Position < m_Length)
|
||
|
{
|
||
|
var ch = m_Text[m_Position];
|
||
|
if (ch == '\\')
|
||
|
{
|
||
|
++m_Position;
|
||
|
if (m_Position == m_Length)
|
||
|
break;
|
||
|
hasEscapes = true;
|
||
|
}
|
||
|
else if (ch == '"')
|
||
|
{
|
||
|
++m_Position;
|
||
|
result = new JsonString
|
||
|
{
|
||
|
text = new Substring(m_Text, startIndex, m_Position - startIndex - 1),
|
||
|
hasEscapes = hasEscapes
|
||
|
};
|
||
|
return true;
|
||
|
}
|
||
|
++m_Position;
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
public bool ParseArrayValue(out JsonValue result)
|
||
|
{
|
||
|
result = default;
|
||
|
|
||
|
SkipWhitespace();
|
||
|
if (m_Position == m_Length || m_Text[m_Position] != '[')
|
||
|
return false;
|
||
|
++m_Position;
|
||
|
|
||
|
if (m_Position == m_Length)
|
||
|
return false;
|
||
|
if (m_Text[m_Position] == ']')
|
||
|
{
|
||
|
// Empty array.
|
||
|
result = new JsonValue { type = JsonValueType.Array };
|
||
|
++m_Position;
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
List<JsonValue> values = null;
|
||
|
if (!m_DryRun)
|
||
|
values = new List<JsonValue>();
|
||
|
|
||
|
while (m_Position < m_Length)
|
||
|
{
|
||
|
if (!ParseValue(out var value))
|
||
|
return false;
|
||
|
if (!m_DryRun)
|
||
|
values.Add(value);
|
||
|
SkipWhitespace();
|
||
|
if (m_Position == m_Length)
|
||
|
return false;
|
||
|
var ch = m_Text[m_Position];
|
||
|
if (ch == ']')
|
||
|
{
|
||
|
++m_Position;
|
||
|
if (!m_DryRun)
|
||
|
result = values;
|
||
|
return true;
|
||
|
}
|
||
|
if (ch == ',')
|
||
|
++m_Position;
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
public bool ParseObjectValue(out JsonValue result)
|
||
|
{
|
||
|
result = default;
|
||
|
|
||
|
if (!ParseToken('{'))
|
||
|
return false;
|
||
|
if (m_Position < m_Length && m_Text[m_Position] == '}')
|
||
|
{
|
||
|
result = new JsonValue { type = JsonValueType.Object };
|
||
|
++m_Position;
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
while (m_Position < m_Length)
|
||
|
{
|
||
|
if (!ParseStringValue(out var propertyName))
|
||
|
return false;
|
||
|
|
||
|
if (!SkipToValue())
|
||
|
return false;
|
||
|
|
||
|
if (!ParseValue(out var propertyValue))
|
||
|
return false;
|
||
|
|
||
|
if (!m_DryRun)
|
||
|
throw new NotImplementedException();
|
||
|
|
||
|
SkipWhitespace();
|
||
|
if (m_Position < m_Length && m_Text[m_Position] == '}')
|
||
|
{
|
||
|
if (!m_DryRun)
|
||
|
throw new NotImplementedException();
|
||
|
++m_Position;
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
public bool ParseNumber(out JsonValue result)
|
||
|
{
|
||
|
result = default;
|
||
|
|
||
|
SkipWhitespace();
|
||
|
if (m_Position == m_Length)
|
||
|
return false;
|
||
|
|
||
|
var negative = false;
|
||
|
var haveFractionalPart = false;
|
||
|
var integralPart = 0L;
|
||
|
var fractionalPart = 0.0;
|
||
|
var fractionalDivisor = 10.0;
|
||
|
var exponent = 0;
|
||
|
|
||
|
// Parse sign.
|
||
|
if (m_Text[m_Position] == '-')
|
||
|
{
|
||
|
negative = true;
|
||
|
++m_Position;
|
||
|
}
|
||
|
|
||
|
if (m_Position == m_Length || !char.IsDigit(m_Text[m_Position]))
|
||
|
return false;
|
||
|
|
||
|
// Parse integral part.
|
||
|
while (m_Position < m_Length)
|
||
|
{
|
||
|
var ch = m_Text[m_Position];
|
||
|
if (ch == '.')
|
||
|
break;
|
||
|
if (ch < '0' || ch > '9')
|
||
|
break;
|
||
|
integralPart = integralPart * 10 + ch - '0';
|
||
|
++m_Position;
|
||
|
}
|
||
|
|
||
|
// Parse fractional part.
|
||
|
if (m_Position < m_Length && m_Text[m_Position] == '.')
|
||
|
{
|
||
|
haveFractionalPart = true;
|
||
|
++m_Position;
|
||
|
if (m_Position == m_Length || !char.IsDigit(m_Text[m_Position]))
|
||
|
return false;
|
||
|
while (m_Position < m_Length)
|
||
|
{
|
||
|
var ch = m_Text[m_Position];
|
||
|
if (ch < '0' || ch > '9')
|
||
|
break;
|
||
|
fractionalPart = (ch - '0') / fractionalDivisor + fractionalPart;
|
||
|
fractionalDivisor *= 10;
|
||
|
++m_Position;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (m_Position < m_Length && (m_Text[m_Position] == 'e' || m_Text[m_Position] == 'E'))
|
||
|
{
|
||
|
++m_Position;
|
||
|
var isNegative = false;
|
||
|
if (m_Position < m_Length && m_Text[m_Position] == '-')
|
||
|
{
|
||
|
isNegative = true;
|
||
|
++m_Position;
|
||
|
}
|
||
|
else if (m_Position < m_Length && m_Text[m_Position] == '+')
|
||
|
{
|
||
|
++m_Position;
|
||
|
}
|
||
|
|
||
|
var multiplier = 1;
|
||
|
while (m_Position < m_Length && char.IsDigit(m_Text[m_Position]))
|
||
|
{
|
||
|
var digit = m_Text[m_Position] - '0';
|
||
|
exponent *= multiplier;
|
||
|
exponent += digit;
|
||
|
multiplier *= 10;
|
||
|
++m_Position;
|
||
|
}
|
||
|
|
||
|
if (isNegative)
|
||
|
exponent *= -1;
|
||
|
}
|
||
|
|
||
|
if (!m_DryRun)
|
||
|
{
|
||
|
if (!haveFractionalPart && exponent == 0)
|
||
|
{
|
||
|
if (negative)
|
||
|
result = -integralPart;
|
||
|
else
|
||
|
result = integralPart;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
float value;
|
||
|
if (negative)
|
||
|
value = (float)-(integralPart + fractionalPart);
|
||
|
else
|
||
|
value = (float)(integralPart + fractionalPart);
|
||
|
if (exponent != 0)
|
||
|
value *= Mathf.Pow(10, exponent);
|
||
|
result = value;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
public bool ParseBooleanValue(out JsonValue result)
|
||
|
{
|
||
|
SkipWhitespace();
|
||
|
if (SkipString("true"))
|
||
|
{
|
||
|
result = true;
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
if (SkipString("false"))
|
||
|
{
|
||
|
result = false;
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
result = default;
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
public bool ParseNullValue(out JsonValue result)
|
||
|
{
|
||
|
result = default;
|
||
|
return SkipString("null");
|
||
|
}
|
||
|
|
||
|
public bool SkipToValue()
|
||
|
{
|
||
|
SkipWhitespace();
|
||
|
if (m_Position == m_Length || m_Text[m_Position] != ':')
|
||
|
return false;
|
||
|
++m_Position;
|
||
|
SkipWhitespace();
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
private bool SkipString(string text)
|
||
|
{
|
||
|
SkipWhitespace();
|
||
|
var length = text.Length;
|
||
|
if (m_Position + length >= m_Length)
|
||
|
return false;
|
||
|
for (var i = 0; i < length; ++i)
|
||
|
{
|
||
|
if (m_Text[m_Position + i] != text[i])
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
m_Position += length;
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
private void SkipWhitespace()
|
||
|
{
|
||
|
while (m_Position < m_Length && char.IsWhiteSpace(m_Text[m_Position]))
|
||
|
++m_Position;
|
||
|
}
|
||
|
|
||
|
public bool isAtEnd => m_Position >= m_Length;
|
||
|
|
||
|
private readonly string m_Text;
|
||
|
private readonly int m_Length;
|
||
|
private int m_Position;
|
||
|
private bool m_MatchAnyElementInArray;
|
||
|
private bool m_DryRun;
|
||
|
|
||
|
public enum JsonValueType
|
||
|
{
|
||
|
None,
|
||
|
Bool,
|
||
|
Real,
|
||
|
Integer,
|
||
|
String,
|
||
|
Array,
|
||
|
Object,
|
||
|
Any,
|
||
|
}
|
||
|
|
||
|
public struct JsonString : IEquatable<JsonString>
|
||
|
{
|
||
|
public Substring text;
|
||
|
public bool hasEscapes;
|
||
|
|
||
|
public override string ToString()
|
||
|
{
|
||
|
if (!hasEscapes)
|
||
|
return text.ToString();
|
||
|
|
||
|
var builder = new StringBuilder();
|
||
|
var length = text.length;
|
||
|
for (var i = 0; i < length; ++i)
|
||
|
{
|
||
|
var ch = text[i];
|
||
|
if (ch == '\\')
|
||
|
{
|
||
|
++i;
|
||
|
if (i == length)
|
||
|
break;
|
||
|
ch = text[i];
|
||
|
}
|
||
|
builder.Append(ch);
|
||
|
}
|
||
|
return builder.ToString();
|
||
|
}
|
||
|
|
||
|
public bool Equals(JsonString other)
|
||
|
{
|
||
|
if (hasEscapes == other.hasEscapes)
|
||
|
return Substring.Compare(text, other.text, StringComparison.InvariantCultureIgnoreCase) == 0;
|
||
|
|
||
|
var thisLength = text.length;
|
||
|
var otherLength = other.text.length;
|
||
|
|
||
|
int thisIndex = 0, otherIndex = 0;
|
||
|
for (; thisIndex < thisLength && otherIndex < otherLength; ++thisIndex, ++otherIndex)
|
||
|
{
|
||
|
var thisChar = text[thisIndex];
|
||
|
var otherChar = other.text[otherIndex];
|
||
|
|
||
|
if (thisChar == '\\')
|
||
|
{
|
||
|
++thisIndex;
|
||
|
if (thisIndex == thisLength)
|
||
|
return false;
|
||
|
thisChar = text[thisIndex];
|
||
|
}
|
||
|
|
||
|
if (otherChar == '\\')
|
||
|
{
|
||
|
++otherIndex;
|
||
|
if (otherIndex == otherLength)
|
||
|
return false;
|
||
|
otherChar = other.text[otherIndex];
|
||
|
}
|
||
|
|
||
|
if (char.ToUpperInvariant(thisChar) != char.ToUpperInvariant(otherChar))
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
return thisIndex == thisLength && otherIndex == otherLength;
|
||
|
}
|
||
|
|
||
|
public override bool Equals(object obj)
|
||
|
{
|
||
|
return obj is JsonString other && Equals(other);
|
||
|
}
|
||
|
|
||
|
public override int GetHashCode()
|
||
|
{
|
||
|
unchecked
|
||
|
{
|
||
|
return (text.GetHashCode() * 397) ^ hasEscapes.GetHashCode();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public static bool operator==(JsonString left, JsonString right)
|
||
|
{
|
||
|
return left.Equals(right);
|
||
|
}
|
||
|
|
||
|
public static bool operator!=(JsonString left, JsonString right)
|
||
|
{
|
||
|
return !left.Equals(right);
|
||
|
}
|
||
|
|
||
|
public static implicit operator JsonString(string str)
|
||
|
{
|
||
|
return new JsonString { text = str };
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public struct JsonValue : IEquatable<JsonValue>
|
||
|
{
|
||
|
public JsonValueType type;
|
||
|
public bool boolValue;
|
||
|
public double realValue;
|
||
|
public long integerValue;
|
||
|
public JsonString stringValue;
|
||
|
public List<JsonValue> arrayValue; // Allocates.
|
||
|
public Dictionary<string, JsonValue> objectValue; // Allocates.
|
||
|
public object anyValue;
|
||
|
|
||
|
public bool ToBoolean()
|
||
|
{
|
||
|
switch (type)
|
||
|
{
|
||
|
case JsonValueType.Bool: return boolValue;
|
||
|
case JsonValueType.Integer: return integerValue != 0;
|
||
|
case JsonValueType.Real: return NumberHelpers.Approximately(0, realValue);
|
||
|
case JsonValueType.String: return Convert.ToBoolean(ToString());
|
||
|
}
|
||
|
return default;
|
||
|
}
|
||
|
|
||
|
public long ToInteger()
|
||
|
{
|
||
|
switch (type)
|
||
|
{
|
||
|
case JsonValueType.Bool: return boolValue ? 1 : 0;
|
||
|
case JsonValueType.Integer: return integerValue;
|
||
|
case JsonValueType.Real: return (long)realValue;
|
||
|
case JsonValueType.String: return Convert.ToInt64(ToString());
|
||
|
}
|
||
|
return default;
|
||
|
}
|
||
|
|
||
|
public double ToDouble()
|
||
|
{
|
||
|
switch (type)
|
||
|
{
|
||
|
case JsonValueType.Bool: return boolValue ? 1 : 0;
|
||
|
case JsonValueType.Integer: return integerValue;
|
||
|
case JsonValueType.Real: return realValue;
|
||
|
case JsonValueType.String: return Convert.ToSingle(ToString());
|
||
|
}
|
||
|
return default;
|
||
|
}
|
||
|
|
||
|
public override string ToString()
|
||
|
{
|
||
|
switch (type)
|
||
|
{
|
||
|
case JsonValueType.None: return "null";
|
||
|
case JsonValueType.Bool: return boolValue.ToString();
|
||
|
case JsonValueType.Integer: return integerValue.ToString(CultureInfo.InvariantCulture);
|
||
|
case JsonValueType.Real: return realValue.ToString(CultureInfo.InvariantCulture);
|
||
|
case JsonValueType.String: return stringValue.ToString();
|
||
|
case JsonValueType.Array:
|
||
|
if (arrayValue == null)
|
||
|
return "[]";
|
||
|
return $"[{string.Join(",", arrayValue.Select(x => x.ToString()))}]";
|
||
|
case JsonValueType.Object:
|
||
|
if (objectValue == null)
|
||
|
return "{}";
|
||
|
var elements = objectValue.Select(pair => $"\"{pair.Key}\" : \"{pair.Value}\"");
|
||
|
return $"{{{string.Join(",", elements)}}}";
|
||
|
case JsonValueType.Any: return anyValue.ToString();
|
||
|
}
|
||
|
return base.ToString();
|
||
|
}
|
||
|
|
||
|
public static implicit operator JsonValue(bool val)
|
||
|
{
|
||
|
return new JsonValue
|
||
|
{
|
||
|
type = JsonValueType.Bool,
|
||
|
boolValue = val
|
||
|
};
|
||
|
}
|
||
|
|
||
|
public static implicit operator JsonValue(long val)
|
||
|
{
|
||
|
return new JsonValue
|
||
|
{
|
||
|
type = JsonValueType.Integer,
|
||
|
integerValue = val
|
||
|
};
|
||
|
}
|
||
|
|
||
|
public static implicit operator JsonValue(double val)
|
||
|
{
|
||
|
return new JsonValue
|
||
|
{
|
||
|
type = JsonValueType.Real,
|
||
|
realValue = val
|
||
|
};
|
||
|
}
|
||
|
|
||
|
public static implicit operator JsonValue(string str)
|
||
|
{
|
||
|
return new JsonValue
|
||
|
{
|
||
|
type = JsonValueType.String,
|
||
|
stringValue = new JsonString { text = str }
|
||
|
};
|
||
|
}
|
||
|
|
||
|
public static implicit operator JsonValue(JsonString str)
|
||
|
{
|
||
|
return new JsonValue
|
||
|
{
|
||
|
type = JsonValueType.String,
|
||
|
stringValue = str
|
||
|
};
|
||
|
}
|
||
|
|
||
|
public static implicit operator JsonValue(List<JsonValue> array)
|
||
|
{
|
||
|
return new JsonValue
|
||
|
{
|
||
|
type = JsonValueType.Array,
|
||
|
arrayValue = array
|
||
|
};
|
||
|
}
|
||
|
|
||
|
public static implicit operator JsonValue(Dictionary<string, JsonValue> obj)
|
||
|
{
|
||
|
return new JsonValue
|
||
|
{
|
||
|
type = JsonValueType.Object,
|
||
|
objectValue = obj
|
||
|
};
|
||
|
}
|
||
|
|
||
|
public static implicit operator JsonValue(Enum val)
|
||
|
{
|
||
|
return new JsonValue
|
||
|
{
|
||
|
type = JsonValueType.Any,
|
||
|
anyValue = val
|
||
|
};
|
||
|
}
|
||
|
|
||
|
public bool Equals(JsonValue other)
|
||
|
{
|
||
|
// Default comparisons.
|
||
|
if (type == other.type)
|
||
|
{
|
||
|
switch (type)
|
||
|
{
|
||
|
case JsonValueType.None: return true;
|
||
|
case JsonValueType.Bool: return boolValue == other.boolValue;
|
||
|
case JsonValueType.Integer: return integerValue == other.integerValue;
|
||
|
case JsonValueType.Real: return NumberHelpers.Approximately(realValue, other.realValue);
|
||
|
case JsonValueType.String: return stringValue == other.stringValue;
|
||
|
case JsonValueType.Object: throw new NotImplementedException();
|
||
|
case JsonValueType.Array: throw new NotImplementedException();
|
||
|
case JsonValueType.Any: return anyValue.Equals(other.anyValue);
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
// anyValue-based comparisons.
|
||
|
if (anyValue != null)
|
||
|
return Equals(anyValue, other);
|
||
|
if (other.anyValue != null)
|
||
|
return Equals(other.anyValue, this);
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
private static bool Equals(object obj, JsonValue value)
|
||
|
{
|
||
|
if (obj == null)
|
||
|
return false;
|
||
|
|
||
|
if (obj is Regex regex)
|
||
|
return regex.IsMatch(value.ToString());
|
||
|
if (obj is string str)
|
||
|
{
|
||
|
switch (value.type)
|
||
|
{
|
||
|
case JsonValueType.String: return value.stringValue == str;
|
||
|
case JsonValueType.Integer: return long.TryParse(str, out var si) && si == value.integerValue;
|
||
|
case JsonValueType.Real:
|
||
|
return double.TryParse(str, out var sf) && NumberHelpers.Approximately(sf, value.realValue);
|
||
|
case JsonValueType.Bool:
|
||
|
if (value.boolValue)
|
||
|
return str == "True" || str == "true" || str == "1";
|
||
|
return str == "False" || str == "false" || str == "0";
|
||
|
}
|
||
|
}
|
||
|
if (obj is float f)
|
||
|
{
|
||
|
if (value.type == JsonValueType.Real)
|
||
|
return NumberHelpers.Approximately(f, value.realValue);
|
||
|
if (value.type == JsonValueType.String)
|
||
|
return float.TryParse(value.ToString(), out var otherF) && Mathf.Approximately(f, otherF);
|
||
|
}
|
||
|
if (obj is double d)
|
||
|
{
|
||
|
if (value.type == JsonValueType.Real)
|
||
|
return NumberHelpers.Approximately(d, value.realValue);
|
||
|
if (value.type == JsonValueType.String)
|
||
|
return double.TryParse(value.ToString(), out var otherD) &&
|
||
|
NumberHelpers.Approximately(d, otherD);
|
||
|
}
|
||
|
if (obj is int i)
|
||
|
{
|
||
|
if (value.type == JsonValueType.Integer)
|
||
|
return i == value.integerValue;
|
||
|
if (value.type == JsonValueType.String)
|
||
|
return int.TryParse(value.ToString(), out var otherI) && i == otherI;
|
||
|
}
|
||
|
if (obj is long l)
|
||
|
{
|
||
|
if (value.type == JsonValueType.Integer)
|
||
|
return l == value.integerValue;
|
||
|
if (value.type == JsonValueType.String)
|
||
|
return long.TryParse(value.ToString(), out var otherL) && l == otherL;
|
||
|
}
|
||
|
if (obj is bool b)
|
||
|
{
|
||
|
if (value.type == JsonValueType.Bool)
|
||
|
return b == value.boolValue;
|
||
|
if (value.type == JsonValueType.String)
|
||
|
{
|
||
|
if (b)
|
||
|
return value.stringValue == "true" || value.stringValue == "True" ||
|
||
|
value.stringValue == "1";
|
||
|
return value.stringValue == "false" || value.stringValue == "False" ||
|
||
|
value.stringValue == "0";
|
||
|
}
|
||
|
}
|
||
|
// NOTE: The enum-based comparisons allocate both on the Convert.ToInt64() and Enum.GetName() path. I've found
|
||
|
// no way to do either comparison in a way that does not allocate.
|
||
|
if (obj is Enum)
|
||
|
{
|
||
|
if (value.type == JsonValueType.Integer)
|
||
|
return Convert.ToInt64(obj) == value.integerValue;
|
||
|
if (value.type == JsonValueType.String)
|
||
|
return value.stringValue == Enum.GetName(obj.GetType(), obj);
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
public override bool Equals(object obj)
|
||
|
{
|
||
|
return obj is JsonValue other && Equals(other);
|
||
|
}
|
||
|
|
||
|
public override int GetHashCode()
|
||
|
{
|
||
|
unchecked
|
||
|
{
|
||
|
var hashCode = (int)type;
|
||
|
hashCode = (hashCode * 397) ^ boolValue.GetHashCode();
|
||
|
hashCode = (hashCode * 397) ^ realValue.GetHashCode();
|
||
|
hashCode = (hashCode * 397) ^ integerValue.GetHashCode();
|
||
|
hashCode = (hashCode * 397) ^ stringValue.GetHashCode();
|
||
|
hashCode = (hashCode * 397) ^ (arrayValue != null ? arrayValue.GetHashCode() : 0);
|
||
|
hashCode = (hashCode * 397) ^ (objectValue != null ? objectValue.GetHashCode() : 0);
|
||
|
hashCode = (hashCode * 397) ^ (anyValue != null ? anyValue.GetHashCode() : 0);
|
||
|
return hashCode;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public static bool operator==(JsonValue left, JsonValue right)
|
||
|
{
|
||
|
return left.Equals(right);
|
||
|
}
|
||
|
|
||
|
public static bool operator!=(JsonValue left, JsonValue right)
|
||
|
{
|
||
|
return !left.Equals(right);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|