react-native-windows/vnext/Microsoft.ReactNative.Managed/JSValue.cs

1055 строки
42 KiB
C#

// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Globalization;
using System.Runtime.InteropServices;
using System.Text;
namespace Microsoft.ReactNative.Managed
{
// JSValueObject is based on Dictionary<string, JSValue> and can be used to initialize Object value in JSValue.
// It is possible to write: JSValueObject{{"X", 4}, {"Y", 5}} or JSValueObject{["X"] = 4, ["Y"] = 5} and assign it to JSValue.
public class JSValueObject : Dictionary<string, JSValue>, IEquatable<JSValueObject>
{
// Default constructor
public JSValueObject() { }
// Create JSValueObject as a shallow copy of 'other'.
public static JSValueObject CopyFrom(IReadOnlyDictionary<string, JSValue> other)
{
JSValueObject obj = new JSValueObject();
foreach (var keyValue in other)
{
obj.Add(keyValue.Key, keyValue.Value);
}
return obj;
}
// Return true if two JSValueObject interfaces are strictly equal to each other.
// Both objects must have the same set of equal properties.
// Property values must be equal.
public static bool Equals(IReadOnlyDictionary<string, JSValue> left, IReadOnlyDictionary<string, JSValue> right)
{
if (left == right) { return true; }
else if (left == null) { return false; }
else if (right == null) { return false; }
else if (left.Count != right.Count) { return false; }
foreach (var entry in left)
{
if (!right.TryGetValue(entry.Key, out var value) || !value.Equals(entry.Value))
{
return false;
}
}
return true;
}
// Return true if this JSValueObject is strictly equal to 'other'.
public bool Equals(JSValueObject other)
{
return Equals(this as IReadOnlyDictionary<string, JSValue>, other as IReadOnlyDictionary<string, JSValue>);
}
// Return true if this JSValueObject is strictly equal to 'other'.
public bool Equals(IReadOnlyDictionary<string, JSValue> other)
{
return Equals(this as IReadOnlyDictionary<string, JSValue>, other);
}
// Return true if this JSValueObject is strictly equal to 'obj'.
public override bool Equals(object obj)
{
return Equals(this as IReadOnlyDictionary<string, JSValue>, obj as IReadOnlyDictionary<string, JSValue>);
}
// True if Equals(left, right)
public static bool operator ==(JSValueObject left, IReadOnlyDictionary<string, JSValue> right)
{
return Equals(left, right);
}
// True if !Equals(left, right)
public static bool operator !=(JSValueObject left, IReadOnlyDictionary<string, JSValue> right)
{
return !Equals(left, right);
}
// True if Equals(left, right)
public static bool operator ==(IReadOnlyDictionary<string, JSValue> left, JSValueObject right)
{
return Equals(left, right);
}
// True if !Equals(left, right)
public static bool operator !=(IReadOnlyDictionary<string, JSValue> left, JSValueObject right)
{
return !Equals(left, right);
}
// Return true if this JSValueObject is strictly equal to other JSValueObject
// after their property values are converted to the same type.
// See JSValue::JSEquals for details about the conversion.
public static bool JSEquals(IReadOnlyDictionary<string, JSValue> left, IReadOnlyDictionary<string, JSValue> right)
{
if (left == right) { return true; }
else if (left == null) { return false; }
else if (right == null) { return false; }
else if (left.Count != right.Count) { return false; }
foreach (var keyValue in left)
{
if (!right.TryGetValue(keyValue.Key, out var rightValue) || !keyValue.Value.JSEquals(rightValue))
{
return false;
}
}
return true;
}
// Calculation of the JSValueObject hash code is quite expensive.
// It is not implemented.
public override int GetHashCode()
{
throw new NotImplementedException("JSValueObject currently does not support hash codes");
}
// Create JSValueObject from IJSValueReader.
public static JSValueObject ReadFrom(IJSValueReader reader) => JSValue.ReadObjectFrom(reader);
// Write this JSValueObject to IJSValueWriter.
public void WriteTo(IJSValueWriter writer) => JSValue.WriteObject(writer, this);
}
// JSValueObject is based on List<JSValue> and can be used to initialize Array value in JSValue.
// It is possible to write: JSValueArray{"X", 42, JSValue.Null, true} and assign it to JSValue.
public class JSValueArray : List<JSValue>, IEquatable<JSValueArray>
{
// Default constructor
public JSValueArray() { }
public static JSValueArray CopyFrom(IReadOnlyList<JSValue> other)
{
JSValueArray arr = new JSValueArray();
foreach (var item in other)
{
arr.Add(item);
}
return arr;
}
// Return true if two JSValuearray interfaces are strictly equal to each other.
// Both arrays must have the same set of items. Items must be equal.
public static bool Equals(IReadOnlyList<JSValue> left, IReadOnlyList<JSValue> right)
{
if (left == right) { return true; }
else if (left == null) { return false; }
else if (right == null) { return false; }
else if (left.Count != right.Count) { return false; }
for (int i = 0; i < left.Count; ++i)
{
if (!left[i].Equals(right[i]))
{
return false;
}
}
return true;
}
// Return true if this JSValueArray is strictly equal to 'other'.
public bool Equals(JSValueArray other)
{
return Equals(this as IReadOnlyList<JSValue>, other as IReadOnlyList<JSValue>);
}
// Return true if this JSValueArray is strictly equal to 'other'.
public bool Equals(IReadOnlyList<JSValue> other)
{
return Equals(this as IReadOnlyList<JSValue>, other);
}
// Return true if this JSValueArray is strictly equal to 'obj'.
public override bool Equals(object obj)
{
return Equals(this as IReadOnlyList<JSValue>, obj as IReadOnlyList<JSValue>);
}
// True if Equals(left, right)
public static bool operator ==(JSValueArray left, IReadOnlyList<JSValue> right)
{
return Equals(left, right);
}
// True if !Equals(left, right)
public static bool operator !=(JSValueArray left, IReadOnlyList<JSValue> right)
{
return !Equals(left, right);
}
// True if Equals(left, right)
public static bool operator ==(IReadOnlyList<JSValue> left, JSValueArray right)
{
return Equals(left, right);
}
// True if !Equals(left, right)
public static bool operator !=(IReadOnlyList<JSValue> left, JSValueArray right)
{
return !Equals(left, right);
}
// Return true if this JSValueArray is strictly equal to other JSValueArray
// after their property values are converted to the same type.
// See JSValue::JSEquals for details about the conversion.
public static bool JSEquals(IReadOnlyList<JSValue> left, IReadOnlyList<JSValue> right)
{
if (left == right) { return true; }
else if (left == null) { return false; }
else if (right == null) { return false; }
else if (left.Count != right.Count) { return false; }
for (int i = 0; i < left.Count; ++i)
{
if (!left[i].JSEquals(right[i]))
{
return false;
}
}
return true;
}
// Calculation of the JSValueArray hash code is quite expensive.
// It is not implemented.
public override int GetHashCode()
{
throw new NotImplementedException("JSValueArray currently does not support hash codes");
}
// Create JSValueArray from IJSValueReader.
public static JSValueArray ReadFrom(IJSValueReader reader) => JSValue.ReadArrayFrom(reader);
// Write this JSValueArray to IJSValueWriter.
public void WriteTo(IJSValueWriter writer) => JSValue.WriteArray(writer, this);
}
// JSValue represents an immutable JavaScript value passed as a parameter.
// It is created to simplify working with IJSValueReader in complex situations.
// For example, when we need to serialize a TypeScript discriminating union and
// we need to read 'kind' property before we process other properties.
//
// JSValue is designed to minimize number of memory allocations:
// - It is a struct that avoids extra memory allocations when it is stored in a container.
// - It avoids boxing simple values by using a union type to store them.
// The JSValue is an immutable and is safe to be used from multiple threads.
// It does not implement GetHashCode() and must not be used as a key in a dictionary.
public struct JSValue : IEquatable<JSValue>
{
public static readonly JSValue Null = default;
public static readonly JSValue EmptyObject = new JSValueObject();
public static readonly JSValue EmptyArray = new JSValueArray();
public static readonly JSValue EmptyString = "";
// Used by AsBoolean() conversion from a string to a Boolean.
private static readonly HashSet<string> StringToBoolean =
new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "true", "1", "yes", "y", "on" };
// The Void type could be used instead of void in generic types.
public struct Void { };
private readonly SimpleValue m_simpleValue;
private readonly object m_objValue;
// Create an Object JSValue.
public JSValue(IReadOnlyDictionary<string, JSValue> value)
{
Type = JSValueType.Object;
m_simpleValue = new SimpleValue();
m_objValue = value ?? EmptyObject.ObjectValue;
}
// Create an Array JSValue.
public JSValue(IReadOnlyList<JSValue> value)
{
Type = JSValueType.Array;
m_simpleValue = new SimpleValue();
m_objValue = value ?? EmptyArray.ArrayValue;
}
// Create a String JSValue.
public JSValue(string value)
{
Type = JSValueType.String;
m_simpleValue = new SimpleValue();
m_objValue = value ?? "";
}
// Create a Boolean JSValue.
public JSValue(bool value)
{
Type = JSValueType.Boolean;
m_simpleValue = new SimpleValue { BooleanValue = value };
m_objValue = null;
}
// Create an Int64 JSValue.
public JSValue(long value)
{
Type = JSValueType.Int64;
m_simpleValue = new SimpleValue { Int64Value = value };
m_objValue = null;
}
// Create a Double JSValue.
public JSValue(double value)
{
Type = JSValueType.Double;
m_simpleValue = new SimpleValue { DoubleValue = value };
m_objValue = null;
}
public static implicit operator JSValue(ReadOnlyDictionary<string, JSValue> value) => new JSValue(value);
public static implicit operator JSValue(Dictionary<string, JSValue> value) => new JSValue(value);
public static implicit operator JSValue(JSValueObject value) => new JSValue(value);
public static implicit operator JSValue(ReadOnlyCollection<JSValue> value) => new JSValue(value);
public static implicit operator JSValue(List<JSValue> value) => new JSValue(value);
public static implicit operator JSValue(JSValueArray value) => new JSValue(value);
public static implicit operator JSValue(string value) => new JSValue(value);
public static implicit operator JSValue(bool value) => new JSValue(value);
public static implicit operator JSValue(sbyte value) => new JSValue(value);
public static implicit operator JSValue(short value) => new JSValue(value);
public static implicit operator JSValue(int value) => new JSValue(value);
public static implicit operator JSValue(long value) => new JSValue(value);
public static implicit operator JSValue(byte value) => new JSValue(value);
public static implicit operator JSValue(ushort value) => new JSValue(value);
public static implicit operator JSValue(uint value) => new JSValue(value);
public static implicit operator JSValue(ulong value) => new JSValue((long)value);
public static implicit operator JSValue(float value) => new JSValue(value);
public static implicit operator JSValue(double value) => new JSValue(value);
// Get JSValue type.
public JSValueType Type { get; }
// Return true if JSValue type is Null.
public bool IsNull => Type == JSValueType.Null;
// Return true and Object value if JSValue type is Object, or false and JSValue.Null otherwise.
public bool TryGetObject(out IReadOnlyDictionary<string, JSValue> value)
{
value = (Type == JSValueType.Object) ? (IReadOnlyDictionary<string, JSValue>)m_objValue : null;
return value != null;
}
// Return true and Array value if JSValue type is Array, or false and JSValue.Null otherwise.
public bool TryGetArray(out IReadOnlyList<JSValue> value)
{
value = (Type == JSValueType.Array) ? (IReadOnlyList<JSValue>)m_objValue : null;
return value != null;
}
// Return true and String value if JSValue type is String, or false and JSValue.Null otherwise.
public bool TryGetString(out string value)
{
value = (Type == JSValueType.String) ? (string)m_objValue : null;
return value != null;
}
// Return true and Boolean value if JSValue type is Boolean, or false and JSValue.Null otherwise.
public bool TryGetBoolean(out bool value)
{
if (Type == JSValueType.Boolean)
{
value = m_simpleValue.BooleanValue;
return true;
}
value = false;
return false;
}
// Return true and Int64 value if JSValue type is Int64, or false and JSValue.Null otherwise.
public bool TryGetInt64(out long value)
{
if (Type == JSValueType.Int64)
{
value = m_simpleValue.Int64Value;
return true;
}
value = 0;
return false;
}
// Return true and Double value if JSValue type is Double, or false and JSValue.Null otherwise.
public bool TryGetDouble(out double value)
{
if (Type == JSValueType.Double)
{
value = m_simpleValue.DoubleValue;
return true;
}
value = 0;
return false;
}
// Return Object representation of JSValue. It is JSValue.EmptyObject if type is not Object.
public IReadOnlyDictionary<string, JSValue> AsObject() =>
Type == JSValueType.Object ? ObjectValue : EmptyObject.ObjectValue;
// Return Array representation of JSValue. It is JSValue.EmptyArray if type is not Array.
public IReadOnlyList<JSValue> AsArray() =>
Type == JSValueType.Array ? ArrayValue : EmptyArray.ArrayValue;
// Return a String representation of JSValue.
// Null is "null".
// Object and Array are empty strings "".
// Boolean is "true" or "false".
// Int64 is converted to string using integer representation.
// Double uses AsJSString() conversion which uses "NaN", "Infinity", and "-Infinity" for special values.
public string AsString()
{
switch (Type)
{
case JSValueType.Null: return JSConverter.NullString;
case JSValueType.String: return StringValue;
case JSValueType.Boolean: return JSConverter.ToJSString(BooleanValue);
case JSValueType.Int64: return Int64Value.ToString();
case JSValueType.Double: return JSConverter.ToJSString(DoubleValue);
default: return "";
}
}
// Return a Boolean representation of JSValue.
// Object and Array are true if they are not empty.
// String is true if it case-insensitively matches "true", "1", "yes", "y", or "on" strings.
// Int64 or Double are false if they are zero or NAN.
public bool AsBoolean()
{
switch (Type)
{
case JSValueType.Object: return ObjectValue.Count != 0;
case JSValueType.Array: return ArrayValue.Count != 0;
case JSValueType.String: return StringToBoolean.Contains(StringValue);
case JSValueType.Boolean: return BooleanValue;
case JSValueType.Int64: return Int64Value != 0;
case JSValueType.Double: return !double.IsNaN(DoubleValue) && DoubleValue != 0;
default: return false;
}
}
// Return an Int8 representation of JSValue. It is the same as (Int8)AsInt64().
public sbyte AsInt8() => (sbyte)AsInt64();
// Return an Int16 representation of JSValue. It is the same as (Int16)AsInt64().
public short AsInt16() => (short)AsInt64();
// Return an Int32 representation of JSValue. It is the same as (Int32)AsInt64().
public int AsInt32() => (int)AsInt64();
// Return an Int64 representation of JSValue.
// String is converted to double first before converting to Int64.
// Boolean is converted to 0 or 1.
// Null, Object, and Array are 0.
public long AsInt64()
{
switch (Type)
{
case JSValueType.String: return JSConverter.ToInt64(JSConverter.ToJSNumber(StringValue));
case JSValueType.Boolean: return BooleanValue ? 1 : 0;
case JSValueType.Int64: return Int64Value;
case JSValueType.Double: return JSConverter.ToInt64(DoubleValue);
default: return 0;
}
}
// Return an Uint8 representation of JSValue. It is the same as (UInt8)AsInt64().
public byte AsUInt8() => (byte)AsInt64();
// Return an UInt16 representation of JSValue. It is the same as (UInt16)AsInt64().
public ushort AsUInt16() => (ushort)AsInt64();
// Return an UInt32 representation of JSValue. It is the same as (UInt32)AsInt64().
public uint AsUInt32() => (uint)AsInt64();
// Return an UInt64 representation of JSValue. It is the same as (UInt64)AsInt64().
public ulong AsUInt64() => (ulong)AsInt64();
// Return a Single representation of JSValue. It is the same as (Single)AsDouble().
public float AsSingle() => (float)AsDouble();
// Return a double representation of JSValue.
// Boolean is converted to 0.0 or 1.0.
// Null, Object, and Array are 0.
public double AsDouble()
{
switch (Type)
{
case JSValueType.String: return JSConverter.ToJSNumber(StringValue);
case JSValueType.Boolean: return BooleanValue ? 1.0 : 0.0;
case JSValueType.Int64: return Int64Value;
case JSValueType.Double: return DoubleValue;
default: return 0;
}
}
// Cast JSValue to String using AsString() call.
public static explicit operator string(JSValue value) => value.AsString();
// Cast JSValue to Boolean using AsBoolean() call.
public static explicit operator bool(JSValue value) => value.AsBoolean();
// Cast JSValue to Int8 using AsInt8() call.
public static explicit operator sbyte(JSValue value) => value.AsInt8();
// Cast JSValue to Int16 using AsInt16() call.
public static explicit operator short(JSValue value) => value.AsInt16();
// Cast JSValue to Int32 using AsInt32() call.
public static explicit operator int(JSValue value) => value.AsInt32();
// Cast JSValue to Int64 using AsInt64() call.
public static explicit operator long(JSValue value) => value.AsInt64();
// Cast JSValue to UInt8 using AsUInt8() call.
public static explicit operator byte(JSValue value) => value.AsUInt8();
// Cast JSValue to UInt16 using AsUInt16() call.
public static explicit operator ushort(JSValue value) => value.AsUInt16();
// Cast JSValue to UInt32 using AsUInt32() call.
public static explicit operator uint(JSValue value) => value.AsUInt32();
// Cast JSValue to UInt64 using AsUInt64() call.
public static explicit operator ulong(JSValue value) => value.AsUInt64();
// Cast JSValue to Single using AsSingle() call.
public static explicit operator float(JSValue value) => value.AsSingle();
// Cast JSValue to Double using AsDouble() call.
public static explicit operator double(JSValue value) => value.AsDouble();
// Return a String representation of JSValue. It is equivalent to JavaScript String(value) result.
public string AsJSString()
{
StringBuilder WriteValue(StringBuilder sb, JSValue node)
{
switch (node.Type)
{
case JSValueType.Null: return sb.Append(JSConverter.NullString);
case JSValueType.Object: return sb.Append(JSConverter.ObjectString);
case JSValueType.Array:
{
bool start = true;
foreach (var item in node.ArrayValue)
{
if (start)
{
start = false;
}
else
{
sb.Append(',');
}
WriteValue(sb, item);
}
return sb;
}
case JSValueType.String: return sb.Append(node.StringValue);
case JSValueType.Boolean: return sb.Append(JSConverter.ToJSString(node.BooleanValue));
case JSValueType.Int64: return sb.Append(node.Int64Value);
case JSValueType.Double: return sb.Append(JSConverter.ToJSString(node.DoubleValue));
default: return sb;
}
}
switch (Type)
{
case JSValueType.Null: return JSConverter.NullString;
case JSValueType.Object: return JSConverter.ObjectString;
case JSValueType.Array:
{
StringBuilder sb = new StringBuilder();
WriteValue(sb, this);
return sb.ToString();
}
case JSValueType.String: return StringValue;
case JSValueType.Boolean: return JSConverter.ToJSString(BooleanValue);
case JSValueType.Int64: return Int64Value.ToString();
case JSValueType.Double: return JSConverter.ToJSString(DoubleValue);
default: return "";
}
}
// Return a Boolean representation of JSValue. It is equivalent to JavaScript Boolean(value) result.
public bool AsJSBoolean()
{
switch (Type)
{
case JSValueType.Object:
case JSValueType.Array: return true;
case JSValueType.String: return !string.IsNullOrEmpty(StringValue);
case JSValueType.Boolean: return BooleanValue;
case JSValueType.Int64: return Int64Value != 0;
case JSValueType.Double: return !double.IsNaN(DoubleValue) && DoubleValue != 0;
default: return false;
}
}
// Return a Double representation of JSValue. It is equivalent to JavaScript Number(value) result.
public double AsJSNumber()
{
switch (Type)
{
case JSValueType.Object:
case JSValueType.Array:
case JSValueType.String: return JSConverter.ToJSNumber(AsJSString());
case JSValueType.Boolean: return BooleanValue ? 1 : 0;
case JSValueType.Int64: return Int64Value;
case JSValueType.Double: return DoubleValue;
default: return 0;
}
}
// Convert JSValue to a readable string that can be used for logging and debugging.
public override string ToString()
{
switch (Type)
{
case JSValueType.Null: return JSConverter.NullString;
case JSValueType.Object:
{
var sb = new StringBuilder();
sb.Append("{");
bool start = true;
foreach (var prop in ObjectValue)
{
if (start)
{
start = false;
}
else
{
sb.Append(", ");
}
sb.Append(prop.Key);
sb.Append(": ");
sb.Append(prop.Value.ToString());
}
sb.Append("}");
return sb.ToString();
}
case JSValueType.Array:
{
var sb = new StringBuilder();
sb.Append("[");
bool start = true;
foreach (var item in ArrayValue)
{
if (start)
{
start = false;
}
else
{
sb.Append(", ");
}
sb.Append(item.ToString());
}
sb.Append("]");
return sb.ToString();
}
case JSValueType.String: return "\"" + StringValue + "\"";
case JSValueType.Boolean: return JSConverter.ToJSString(BooleanValue);
case JSValueType.Int64: return Int64Value.ToString();
case JSValueType.Double: return JSConverter.ToJSString(DoubleValue);
default: return "<Unexpected>";
}
}
// Return value T that is created from JSValue using a ReadValue extension function.
// Default T is constructed by using default constructor.
public T To<T>() => new JSValueTreeReader(this).ReadValue<T>();
// Create JSValue from a type that has WriteValue extension method defined to write to IJSValueWriter.
public JSValue From<T>(T value)
{
var writer = new JSValueTreeWriter();
writer.WriteValue(value);
return writer.TakeValue();
}
// Return property count if JSValue is Object, or 0 otherwise.
public int PropertyCount => Type == JSValueType.Object ? ObjectValue.Count : 0;
// Try to get an object property value if JSValue type is Object and the property is found.
// It returns false and outputs JSValue.Null otherwise.
public bool TryGetObjectProperty(string propertyName, out JSValue value)
{
if (TryGetObject(out var objectValue) && objectValue.TryGetValue(propertyName, out value))
{
return true;
}
value = Null;
return false;
}
// Get an object property value if JSValue type is Object and the property is found,
// or JSValue.Null otherwise.
public JSValue GetObjectProperty(string propertyName)
{
TryGetObjectProperty(propertyName, out JSValue result);
return result;
}
// Get an object property value if JSValue type is Object and the property is found,
// or JSValue.Null otherwise.
public JSValue this[string propertyName] => GetObjectProperty(propertyName);
// Return item count if JSValue is Array, or 0 otherwise.
public int ItemCount => Type == JSValueType.Array ? ArrayValue.Count : 0;
// Try to get an array item if JSValue type is Array and the index is in bounds.
// It returns false and outputs JSValue.Null otherwise.
public bool TryGetArrayItem(int index, out JSValue value)
{
if (index >= 0 && TryGetArray(out var arrayValue) && index < arrayValue.Count)
{
value = arrayValue[index];
return true;
}
value = Null;
return false;
}
// Get an array item if JSValue type is Array and the index is in bounds,
// or JSValue.Null otherwise.
public JSValue GetArrayItem(int index)
{
TryGetArrayItem(index, out JSValue result);
return result;
}
// Get an array item if JSValue type is Array and the index is in bounds,
// or JSValue.Null otherwise.
public JSValue this[int index] => GetArrayItem(index);
// Return true if this JSValue is strictly equal to JSValue.
// Compared values must have the same type and value.
//
// The behavior is similar to JavaScript === operator except for Object and Array where
// this functions does a deep structured comparison instead of pointer equality.
public bool Equals(JSValue other)
{
if (Type != other.Type)
{
return false;
}
switch (Type)
{
case JSValueType.Null: return true;
case JSValueType.Object: return JSValueObject.Equals(ObjectValue, other.ObjectValue);
case JSValueType.Array: return JSValueArray.Equals(ArrayValue, other.ArrayValue);
case JSValueType.String: return StringValue == other.StringValue;
case JSValueType.Boolean: return BooleanValue == other.BooleanValue;
case JSValueType.Int64: return Int64Value == other.Int64Value;
case JSValueType.Double: return DoubleValue == other.DoubleValue;
default: return false;
}
}
// Return true if obj is a JSValue it is strictly equal to this JSValue.
public override bool Equals(object obj)
{
return (obj is JSValue) && Equals((JSValue)obj);
}
// True if left.Equals(right)
public static bool operator ==(JSValue left, JSValue right)
{
return left.Equals(right);
}
//! True if !left.Equals(right)
public static bool operator !=(JSValue left, JSValue right)
{
return !left.Equals(right);
}
// Return true if this JSValue is strictly equal to JSValue after they are converted to the same type.
//
// Null is not converted to any other type before comparison.
// Object and Array types are converted first to a String type using AsString() before comparing
// with other types, and then we apply the same rules as for the String type.
// String is converted to Double before comparing with Boolean, Int64, or Double.
// Boolean is converted to 1.0 and +0.0 when comparing with String, Int64, or Double.
// Int64 is converted to Double before comparing with other types.
//
// The behavior is similar to JavaScript == operator except for Object and Array for which
// this functions does a deep structured comparison using JSEquals instead
// of pointer equality.
public bool JSEquals(JSValue other)
{
if (Type == other.Type)
{
switch (Type)
{
case JSValueType.Object:
return JSValueObject.JSEquals(ObjectValue, other.ObjectValue);
case JSValueType.Array:
return JSValueArray.JSEquals(ArrayValue, other.ArrayValue);
default:
return Equals(other);
}
}
else if (Type == JSValueType.Null || other.Type == JSValueType.Null)
{
return false;
}
// If one of the types Boolean, Int64, or Double, then compare as Numbers,
// otherwise compare as strings.
JSValueType greatestType = Type > other.Type ? Type : other.Type;
if (greatestType >= JSValueType.Boolean)
{
return AsJSNumber() == other.AsJSNumber();
}
else
{
return AsJSString() == other.AsJSString();
}
}
// Calculation of the JSValue hash code is quite expensive.
// It is not implemented.
public override int GetHashCode()
{
throw new NotImplementedException("JSValue currently does not support hash codes");
}
// Create JSValue from IJSValueReader.
public static JSValue ReadFrom(IJSValueReader reader)
{
if (reader is IJSValueTreeReader treeReader)
{
return treeReader.Current;
}
return InternalReadFrom(reader);
}
// Create JSValueObject from IJSValueReader.
public static JSValueObject ReadObjectFrom(IJSValueReader reader)
{
if (reader.ValueType == JSValueType.Object)
{
if (reader is IJSValueTreeReader treeReader)
{
return JSValueObject.CopyFrom(treeReader.Current.ObjectValue);
}
return InternalReadObjectFrom(reader);
}
return new JSValueObject();
}
// Create JSValueArray from IJSValueReader.
public static JSValueArray ReadArrayFrom(IJSValueReader reader)
{
if (reader.ValueType == JSValueType.Array)
{
if (reader is IJSValueTreeReader treeReader)
{
return JSValueArray.CopyFrom(treeReader.Current.ArrayValue);
}
return InternalReadArrayFrom(reader);
}
return new JSValueArray();
}
// Write this JSValue to IJSValueWriter.
public void WriteTo(IJSValueWriter writer)
{
switch (Type)
{
case JSValueType.Null: writer.WriteNull(); break;
case JSValueType.Object: WriteObject(writer, ObjectValue); break;
case JSValueType.Array: WriteArray(writer, ArrayValue); break;
case JSValueType.String: writer.WriteString(StringValue); break;
case JSValueType.Boolean: writer.WriteBoolean(BooleanValue); break;
case JSValueType.Int64: writer.WriteInt64(Int64Value); break;
case JSValueType.Double: writer.WriteDouble(DoubleValue); break;
}
}
// Write JSValueObject key-value pairs to IJSValueWriter.
public static void WriteObject(IJSValueWriter writer, IEnumerable<KeyValuePair<string, JSValue>> value)
{
writer.WriteObjectBegin();
foreach (var keyValue in value)
{
writer.WritePropertyName(keyValue.Key);
keyValue.Value.WriteTo(writer);
}
writer.WriteObjectEnd();
}
// Write JSValueArray items to IJSValueWriter.
public static void WriteArray(IJSValueWriter writer, IEnumerable<JSValue> value)
{
writer.WriteArrayBegin();
foreach (var item in value)
{
item.WriteTo(writer);
}
writer.WriteArrayEnd();
}
private static JSValue InternalReadFrom(IJSValueReader reader)
{
switch (reader.ValueType)
{
case JSValueType.Null: return Null;
case JSValueType.Object: return new JSValue(InternalReadObjectFrom(reader));
case JSValueType.Array: return new JSValue(InternalReadArrayFrom(reader));
case JSValueType.String: return new JSValue(reader.GetString());
case JSValueType.Boolean: return new JSValue(reader.GetBoolean());
case JSValueType.Int64: return new JSValue(reader.GetInt64());
case JSValueType.Double: return new JSValue(reader.GetDouble());
default: throw new ReactException("Unexpected JSValueType");
}
}
private static JSValueObject InternalReadObjectFrom(IJSValueReader reader)
{
var jsObject = new JSValueObject();
while (reader.GetNextObjectProperty(out string propertyName))
{
jsObject.Add(propertyName, InternalReadFrom(reader));
}
return jsObject;
}
private static JSValueArray InternalReadArrayFrom(IJSValueReader reader)
{
var jsArray = new JSValueArray();
while (reader.GetNextArrayItem())
{
jsArray.Add(InternalReadFrom(reader));
}
return jsArray;
}
#region Obsolete members
[Obsolete("Use TryGetObject or AsObject")] public IReadOnlyDictionary<string, JSValue> Object => (IReadOnlyDictionary<string, JSValue>)m_objValue;
[Obsolete("Use TryGetArray or AsArray")] public IReadOnlyList<JSValue> Array => (IReadOnlyList<JSValue>)m_objValue;
[Obsolete("Use TryGetString, AsString, or AsJSString")] public string String => (string)m_objValue;
[Obsolete("Use TryGetBoolean, AsBoolean, or AsJSBoolean")] public bool Boolean => m_simpleValue.BooleanValue;
[Obsolete("Use TryGetInt64 or AsInt64")] public long Int64 => m_simpleValue.Int64Value;
[Obsolete("Use TryGetDouble, AsDouble, or AsJSNumber")] public double Double => m_simpleValue.DoubleValue;
[Obsolete("Use ReadObjectFrom")]
public static Dictionary<string, JSValue> ReadObjectPropertiesFrom(IJSValueReader reader)
{
return ReadObjectFrom(reader);
}
[Obsolete("Use ReadArrayFrom")]
public static List<JSValue> ReadArrayItemsFrom(IJSValueReader reader)
{
return ReadArrayFrom(reader);
}
#endregion
private IReadOnlyDictionary<string, JSValue> ObjectValue => (IReadOnlyDictionary<string, JSValue>)m_objValue;
private IReadOnlyList<JSValue> ArrayValue => (IReadOnlyList<JSValue>)m_objValue;
private string StringValue => (string)m_objValue;
private bool BooleanValue => m_simpleValue.BooleanValue;
private long Int64Value => m_simpleValue.Int64Value;
private double DoubleValue => m_simpleValue.DoubleValue;
// This is a 'union' type that may store in the same location bool, long, or double.
[StructLayout(LayoutKind.Explicit)]
private struct SimpleValue
{
[FieldOffset(0)] public bool BooleanValue;
[FieldOffset(0)] public long Int64Value;
[FieldOffset(0)] public double DoubleValue;
}
private static class JSConverter
{
public static readonly string NullString = "null";
public static readonly string ObjectString = "[object Object]";
public static string ToJSString(bool value)
{
return value ? "true" : "false";
}
public static string ToJSString(double value)
{
if (double.IsNaN(value))
{
return "NaN";
}
else if (double.IsPositiveInfinity(value))
{
return "Infinity";
}
else if (double.IsNegativeInfinity(value))
{
return "-Infinity";
}
else
{
return value.ToString();
}
}
public static double ToJSNumber(string value)
{
var trimmed = value.Trim();
if (trimmed.Length == 0)
{
return 0;
}
// NumberStyles.Float does not allow numbers to have thousand's commas insider.
return double.TryParse(trimmed, NumberStyles.Float, CultureInfo.InvariantCulture, out double doubleValue)
? doubleValue : double.NaN;
}
public static long ToInt64(double value) =>
(long.MinValue <= value && value <= long.MaxValue) ? unchecked((long)value) : 0;
}
}
}