diff --git a/src/System.Text.Json/ref/System.Text.Json.cs b/src/System.Text.Json/ref/System.Text.Json.cs index c9560f4e005e..1677a542ee48 100644 --- a/src/System.Text.Json/ref/System.Text.Json.cs +++ b/src/System.Text.Json/ref/System.Text.Json.cs @@ -156,6 +156,97 @@ protected JsonNamingPolicy() { } public static System.Text.Json.JsonNamingPolicy CamelCase { get { throw null; } } public abstract string ConvertName(string name); } + public abstract partial class JsonNode + { + internal JsonNode() { } + } + public sealed partial class JsonNumber : System.Text.Json.JsonNode, System.IEquatable + { + public JsonNumber() { } + public JsonNumber(byte value) { } + public JsonNumber(decimal value) { } + public JsonNumber(double value) { } + public JsonNumber(short value) { } + public JsonNumber(int value) { } + public JsonNumber(long value) { } + [System.CLSCompliantAttribute(false)] + public JsonNumber(sbyte value) { } + public JsonNumber(float value) { } + public JsonNumber(string value) { } + [System.CLSCompliantAttribute(false)] + public JsonNumber(ushort value) { } + [System.CLSCompliantAttribute(false)] + public JsonNumber(uint value) { } + [System.CLSCompliantAttribute(false)] + public JsonNumber(ulong value) { } + public override bool Equals(object obj) { throw null; } + public bool Equals(System.Text.Json.JsonNumber other) { throw null; } + public byte GetByte() { throw null; } + public decimal GetDecimal() { throw null; } + public double GetDouble() { throw null; } + public override int GetHashCode() { throw null; } + public short GetInt16() { throw null; } + public int GetInt32() { throw null; } + public long GetInt64() { throw null; } + [System.CLSCompliantAttribute(false)] + public sbyte GetSByte() { throw null; } + public float GetSingle() { throw null; } + [System.CLSCompliantAttribute(false)] + public ushort GetUInt16() { throw null; } + [System.CLSCompliantAttribute(false)] + public uint GetUInt32() { throw null; } + [System.CLSCompliantAttribute(false)] + public ulong GetUInt64() { throw null; } + public static bool operator ==(System.Text.Json.JsonNumber left, System.Text.Json.JsonNumber right) { throw null; } + public static implicit operator System.Text.Json.JsonNumber (byte value) { throw null; } + public static implicit operator System.Text.Json.JsonNumber (decimal value) { throw null; } + public static implicit operator System.Text.Json.JsonNumber (double value) { throw null; } + public static implicit operator System.Text.Json.JsonNumber (short value) { throw null; } + public static implicit operator System.Text.Json.JsonNumber (int value) { throw null; } + public static implicit operator System.Text.Json.JsonNumber (long value) { throw null; } + [System.CLSCompliantAttribute(false)] + public static implicit operator System.Text.Json.JsonNumber (sbyte value) { throw null; } + public static implicit operator System.Text.Json.JsonNumber (float value) { throw null; } + [System.CLSCompliantAttribute(false)] + public static implicit operator System.Text.Json.JsonNumber (ushort value) { throw null; } + [System.CLSCompliantAttribute(false)] + public static implicit operator System.Text.Json.JsonNumber (uint value) { throw null; } + [System.CLSCompliantAttribute(false)] + public static implicit operator System.Text.Json.JsonNumber (ulong value) { throw null; } + public static bool operator !=(System.Text.Json.JsonNumber left, System.Text.Json.JsonNumber right) { throw null; } + public void SetByte(byte value) { } + public void SetDecimal(decimal value) { } + public void SetDouble(double value) { } + public void SetFormattedValue(string value) { } + public void SetInt16(short value) { } + public void SetInt32(int value) { } + public void SetInt64(long value) { } + [System.CLSCompliantAttribute(false)] + public void SetSByte(sbyte value) { } + public void SetSingle(float value) { } + [System.CLSCompliantAttribute(false)] + public void SetUInt16(ushort value) { } + [System.CLSCompliantAttribute(false)] + public void SetUInt32(uint value) { } + [System.CLSCompliantAttribute(false)] + public void SetUInt64(ulong value) { } + public override string ToString() { throw null; } + public bool TryGetByte(out byte value) { throw null; } + public bool TryGetDecimal(out decimal value) { throw null; } + public bool TryGetDouble(out double value) { throw null; } + public bool TryGetInt16(out short value) { throw null; } + public bool TryGetInt32(out int value) { throw null; } + public bool TryGetInt64(out long value) { throw null; } + [System.CLSCompliantAttribute(false)] + public bool TryGetSByte(out sbyte value) { throw null; } + public bool TryGetSingle(out float value) { throw null; } + [System.CLSCompliantAttribute(false)] + public bool TryGetUInt16(out ushort value) { throw null; } + [System.CLSCompliantAttribute(false)] + public bool TryGetUInt32(out uint value) { throw null; } + [System.CLSCompliantAttribute(false)] + public bool TryGetUInt64(out ulong value) { throw null; } + } public readonly partial struct JsonProperty { private readonly object _dummy; diff --git a/src/System.Text.Json/src/Resources/Strings.resx b/src/System.Text.Json/src/Resources/Strings.resx index 2958e5911177..898e45978013 100644 --- a/src/System.Text.Json/src/Resources/Strings.resx +++ b/src/System.Text.Json/src/Resources/Strings.resx @@ -426,4 +426,7 @@ A possible object cycle was detected which is not supported. This can either be due to a cycle or if the object depth is larger than the maximum allowed depth of {0}. - \ No newline at end of file + + Expected a number, but instead got empty string. + + diff --git a/src/System.Text.Json/src/System.Text.Json.csproj b/src/System.Text.Json/src/System.Text.Json.csproj index 8d2f4ddc7fb4..fcee8a8fbd59 100644 --- a/src/System.Text.Json/src/System.Text.Json.csproj +++ b/src/System.Text.Json/src/System.Text.Json.csproj @@ -208,4 +208,8 @@ + + + + diff --git a/src/System.Text.Json/src/System/Text/Json/Node/JsonNode.cs b/src/System.Text.Json/src/System/Text/Json/Node/JsonNode.cs new file mode 100644 index 000000000000..8b40fec4b3c8 --- /dev/null +++ b/src/System.Text.Json/src/System/Text/Json/Node/JsonNode.cs @@ -0,0 +1,14 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace System.Text.Json +{ + /// + /// The base class that represents a single node within a JSON document. + /// + public abstract class JsonNode + { + private protected JsonNode() { } + } +} diff --git a/src/System.Text.Json/src/System/Text/Json/Node/JsonNumber.cs b/src/System.Text.Json/src/System/Text/Json/Node/JsonNumber.cs new file mode 100644 index 000000000000..57b4c754bcfc --- /dev/null +++ b/src/System.Text.Json/src/System/Text/Json/Node/JsonNumber.cs @@ -0,0 +1,688 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Globalization; + +namespace System.Text.Json +{ + /// + /// Represents a numeric JSON value. + /// + public sealed class JsonNumber : JsonNode, IEquatable + { + private string _value; + + /// + /// Initializes a new instance of the class representing the value 0. + /// + public JsonNumber() => _value = "0"; + + /// + /// Initializes a new instance of the class representing a specified value. + /// + /// The string representation of a numeric value in a legal JSON number format. + /// + /// Provided value is null. + /// + /// + /// Provided value is not in a legal JSON number format. + /// + /// Provided value is stored in the same format as passed. + public JsonNumber(string value) => SetFormattedValue(value); + + /// + /// Initializes a new instance of the class from a value. + /// + /// The value to represent as a JSON number. + public JsonNumber(byte value) => SetByte(value); + + /// + /// Initializes a new instance of the class from an value. + /// + /// The value to represent as a JSON number. + public JsonNumber(short value) => SetInt16(value); + + /// + /// Initializes a new instance of the class from an value. + /// + /// The value to represent as a JSON number. + public JsonNumber(int value) => SetInt32(value); + + /// + /// Initializes a new instance of the class from an value. + /// + /// The value to represent as a JSON number. + public JsonNumber(long value) => SetInt64(value); + + /// + /// Initializes a new instance of the class from a value. + /// + /// A value to represent as a JSON number. + /// + /// Provided value is not in a legal JSON number format. + /// + public JsonNumber(float value) => SetSingle(value); + + /// + /// Initializes a new instance of the class from a value. + /// + /// The value to represent as a JSON number. + /// + /// Provided value is not in a legal JSON number format. + /// + public JsonNumber(double value) => SetDouble(value); + + /// + /// Initializes a new instance of the class from a value. + /// + /// The value to represent as a JSON number. + [CLSCompliant(false)] + public JsonNumber(sbyte value) => SetSByte(value); + + /// + /// Initializes a new instance of the class from a value. + /// + /// The value to represent as a JSON number. + [CLSCompliant(false)] + public JsonNumber(ushort value) => SetUInt16(value); + + /// + /// Initializes a new instance of the class from a value. + /// + /// The value to represent as a JSON number. + [CLSCompliant(false)] + public JsonNumber(uint value) => SetUInt32(value); + + /// + /// Initializes a new instance of the class from a value. + /// + /// The value to represent as a JSON number. + [CLSCompliant(false)] + public JsonNumber(ulong value) => SetUInt64(value); + + /// + /// Initializes a new instance of the class from a value. + /// + /// The value to represent as a JSON number. + public JsonNumber(decimal value) => SetDecimal(value); + + /// + /// Converts the numeric value of this instance to its equivalent string representation. + /// + /// The string representation of the value of this instance. + /// + /// Returns exactly the same value as it was set using . + /// + public override string ToString() => _value; + + /// + /// Converts the numeric value of this instance to its equivalent. + /// + /// A equivalent to the number stored by this instance. + /// + /// represents a number less than or greater than . + /// + /// + /// represents a number in a format not compliant with . + /// + public byte GetByte() => byte.Parse(_value); + + /// + /// Converts the numeric value of this instance to its equivalent. + /// + /// A equivalent to the number stored by this instance. + /// + /// represents a number less than or greater than . + /// + /// + /// represents a number in a format not compliant with . + /// + public short GetInt16() => short.Parse(_value); + + /// + /// Converts the numeric value of this instance to its equivalent. + /// + /// A equivalent to the number stored by this instance. + /// + /// represents a number less than or greater than . + /// + /// + /// represents a number in a format not compliant with . + /// + public int GetInt32() => int.Parse(_value); + + /// + /// Converts the numeric value of this instance to its equivalent. + /// + /// A equivalent to the number stored by this instance. + /// + /// represents a number less than or greater than . + /// + /// + /// represents a number in a format not compliant with . + /// + public long GetInt64() => long.Parse(_value); + + /// + /// Converts the numeric value of this instance to its equivalent. + /// + /// A equivalent to the number stored by this instance. + /// + /// represents a number less than or greater than . + /// + /// + /// represents a number in a format not compliant with . + /// + /// + /// On .NET Core this method does not return for values larger than + /// (or smaller than ), + /// instead is returned and (or + /// ) is emitted. + /// + /// + /// Allows scientific mode. + /// + public float GetSingle() => float.Parse(_value, NumberStyles.Float, CultureInfo.InvariantCulture); + + /// + /// Converts the numeric value of this instance to its equivalent. + /// + /// A equivalent to the number stored by this instance. + /// + /// represents a number less than or greater than . + /// + /// + /// represents a number in a format not compliant with . + /// + /// + /// On .NET Core this method does not return for values larger than + /// (or smaller than ), + /// instead is returned and (or + /// ) is emitted. + /// + /// + /// Allows scientific mode. + /// + public double GetDouble() => double.Parse(_value, NumberStyles.Float, CultureInfo.InvariantCulture); + + /// + /// Converts the numeric value of this instance to its equivalent. + /// + /// A equivalent to the number stored by this instance. + /// + /// represents a number less than or greater than . + /// + /// + /// represents a number in a format not compliant with . + /// + [CLSCompliant(false)] + public sbyte GetSByte() => sbyte.Parse(_value); + + /// + /// Converts the numeric value of this instance to its equivalent. + /// + /// A equivalent to the number stored by this instance. + /// + /// represents a number less than or greater than . + /// + /// + /// represents a number in a format not compliant with . + /// + [CLSCompliant(false)] + public ushort GetUInt16() => ushort.Parse(_value); + + /// + /// Converts the numeric value of this instance to its equivalent. + /// + /// A equivalent to the number stored by this instance. + /// + /// represents a number less than or greater than . + /// + /// + /// represents a number in a format not compliant with . + /// + [CLSCompliant(false)] + public uint GetUInt32() => uint.Parse(_value); + + /// + /// Converts the numeric value of this instance to its equivalent. + /// + /// A equivalent to the number stored by this instance. + /// + /// represents a number less than or greater than . + /// + /// + /// represents a number in a format not compliant with . + /// + [CLSCompliant(false)] + public ulong GetUInt64() => ulong.Parse(_value); + + /// + /// Converts the numeric value of this instance to its equivalent. + /// + /// A equivalent to the number stored by this instance. + /// + /// represents a number less than or greater than . + /// + /// + /// represents a number in a format not compliant with . + /// + public decimal GetDecimal() => decimal.Parse(_value, NumberStyles.Float, CultureInfo.InvariantCulture); + + /// + /// Converts the numeric value of this instance to its equivalent. + /// A return value indicates whether the conversion succeeded. + /// + /// + /// When this method returns, contains the value equivalent of the number contained in this instance, + /// if the conversion succeeded, or zero if the conversion failed. + /// + /// + /// if instance was converted successfully; + /// otherwise, + /// + public bool TryGetByte(out byte value) => byte.TryParse(_value, out value); + + /// + /// Converts the numeric value of this instance to its equivalent. + /// A return value indicates whether the conversion succeeded. + /// + /// + /// When this method returns, contains the value equivalent of the number contained in this instance, + /// if the conversion succeeded, or zero if the conversion failed. + /// + /// + /// if instance was converted successfully; + /// otherwise, + /// + public bool TryGetInt16(out short value) => short.TryParse(_value, out value); + + /// + /// Converts the numeric value of this instance to its equivalent. + /// A return value indicates whether the conversion succeeded. + /// + /// + /// When this method returns, contains the value equivalent of the number contained in this instance, + /// if the conversion succeeded, or zero if the conversion failed. + /// + /// + /// if instance was converted successfully; + /// otherwise, + /// + public bool TryGetInt32(out int value) => int.TryParse(_value, out value); + + /// + /// Converts the numeric value of this instance to its equivalent. + /// A return value indicates whether the conversion succeeded. + /// + /// + /// When this method returns, contains the value equivalent of the number contained in this instance, + /// if the conversion succeeded, or zero if the conversion failed. + /// + /// + /// if instance was converted successfully; + /// otherwise, + /// + public bool TryGetInt64(out long value) => long.TryParse(_value, out value); + + /// + /// Converts the numeric value of this instance to its equivalent. + /// A return value indicates whether the conversion succeeded. + /// + /// + /// When this method returns, contains the value equivalent of the number contained in this instance, + /// if the conversion succeeded, or zero if the conversion failed. + /// + /// + /// if instance was converted successfully; + /// otherwise, + /// + /// + /// On .NET Core this method does not return for values larger than + /// (or smaller than ), + /// instead is returned and (or + /// ) is emitted. + /// + public bool TryGetSingle(out float value) => float.TryParse(_value, NumberStyles.Float, CultureInfo.InvariantCulture, out value); + + /// + /// Converts the numeric value of this instance to its equivalent. + /// A return value indicates whether the conversion succeeded. + /// + /// + /// When this method returns, contains the value equivalent of the number contained in this instance, + /// if the conversion succeeded, or zero if the conversion failed. + /// + /// + /// if instance was converted successfully; + /// otherwise, + /// + /// + /// On .NET Core this method does not return for values larger than + /// (or smaller than ), + /// instead is returned and (or + /// ) is emitted. + /// + public bool TryGetDouble(out double value) => double.TryParse(_value, NumberStyles.Float, CultureInfo.InvariantCulture, out value); + + /// + /// Converts the numeric value of this instance to its equivalent. + /// A return value indicates whether the conversion succeeded. + /// + /// + /// When this method returns, contains the value equivalent of the number contained in this instance, + /// if the conversion succeeded, or zero if the conversion failed. + /// + /// + /// if instance was converted successfully; + /// otherwise, + /// + [CLSCompliant(false)] + public bool TryGetSByte(out sbyte value) => sbyte.TryParse(_value, out value); + + /// + /// Converts the numeric value of this instance to its equivalent. + /// A return value indicates whether the conversion succeeded. + /// + /// + /// When this method returns, contains the value equivalent of the number contained in this instance, + /// if the conversion succeeded, or zero if the conversion failed. + /// + /// + /// if instance was converted successfully; + /// otherwise, + /// + [CLSCompliant(false)] + public bool TryGetUInt16(out ushort value) => ushort.TryParse(_value, out value); + + /// + /// Converts the numeric value of this instance to its equivalent. + /// A return value indicates whether the conversion succeeded. + /// + /// + /// When this method returns, contains the value equivalent of the number contained in this instance, + /// if the conversion succeeded, or zero if the conversion failed. + /// + /// + /// if instance was converted successfully; + /// otherwise, + /// + [CLSCompliant(false)] + public bool TryGetUInt32(out uint value) => uint.TryParse(_value, out value); + + /// + /// Converts the numeric value of this instance to its equivalent. + /// A return value indicates whether the conversion succeeded. + /// + /// + /// When this method returns, contains the value equivalent of the number contained in this instance, + /// if the conversion succeeded, or zero if the conversion failed. + /// + /// + /// if instance was converted successfully; + /// otherwise, + /// + [CLSCompliant(false)] + public bool TryGetUInt64(out ulong value) => ulong.TryParse(_value, out value); + + /// + /// Converts the numeric value of this instance to its equivalent. + /// A return value indicates whether the conversion succeeded. + /// + /// + /// When this method returns, contains the value equivalent of the number contained in this instance, + /// if the conversion succeeded, or zero if the conversion failed. + /// + /// + /// if instance was converted successfully; + /// otherwise, + /// + public bool TryGetDecimal(out decimal value) => decimal.TryParse(_value, NumberStyles.Float, CultureInfo.InvariantCulture, out value); + + /// + /// Changes the numeric value of this instance to represent a specified value. + /// + /// The string representation of a numeric value in a legal JSON number format. + /// + /// Provided value is null. + /// + /// + /// Provided value is not in a legal JSON number format. + /// + /// Provided value is stored in the same format as passed. + public void SetFormattedValue(string value) + { + if (string.IsNullOrEmpty(value)) + { + if (value == null) + { + throw new ArgumentNullException(nameof(value)); + } + throw new ArgumentException(SR.EmptyStringToInitializeNumber, nameof(value)); + } + + JsonWriterHelper.ValidateNumber(Encoding.UTF8.GetBytes(value).AsSpan()); + _value = value; + } + + /// + /// Changes the numeric value of this instance to represent a specified value. + /// + /// The value to represent as a JSON number. + public void SetByte(byte value) => _value = value.ToString(); + + /// + /// Changes the numeric value of this instance to represent a specified value. + /// + /// The value to represent as a JSON number. + public void SetInt16(short value) => _value = value.ToString(); + + /// + /// Changes the numeric value of this instance to represent a specified value. + /// + /// The value to represent as a JSON number. + public void SetInt32(int value) => _value = value.ToString(); + + /// + /// Changes the numeric value of this instance to represent a specified value. + /// + /// The value to represent as a JSON number. + public void SetInt64(long value) => _value = value.ToString(); + + /// + /// Changes the numeric value of this instance to represent a specified value. + /// + /// The value to represent as a JSON number. + /// + /// Provided value is not in a legal JSON number format. + /// + public void SetSingle(float value) + { + JsonWriterHelper.ValidateSingle(value); + _value = value.ToString(CultureInfo.InvariantCulture); + } + /// + /// Changes the numeric value of this instance to represent a specified value. + /// + /// The value to represent as a JSON number. + /// + /// Provided value is not in a legal JSON number format. + /// + public void SetDouble(double value) + { + JsonWriterHelper.ValidateDouble(value); + _value = value.ToString(CultureInfo.InvariantCulture); + } + + /// + /// Changes the numeric value of this instance to represent a specified value. + /// + /// The value to represent as a JSON number. + [CLSCompliant(false)] + public void SetSByte(sbyte value) => _value = value.ToString(); + + /// + /// Changes the numeric value of this instance to represent a specified value. + /// + /// The value to represent as a JSON number. + [CLSCompliant(false)] + public void SetUInt16(ushort value) => _value = value.ToString(); + + /// + /// Changes the numeric value of this instance to represent a specified value. + /// + /// The value to represent as a JSON number. + [CLSCompliant(false)] + public void SetUInt32(uint value) => _value = value.ToString(); + + /// + /// Changes the numeric value of this instance to represent a specified value. + /// + /// The value to represent as a JSON number. + [CLSCompliant(false)] + public void SetUInt64(ulong value) => _value = value.ToString(); + + /// + /// Changes the numeric value of this instance to represent a specified value. + /// + /// The value to represent as a JSON number. + public void SetDecimal(decimal value) => _value = value.ToString(CultureInfo.InvariantCulture); + + + /// + /// Converts a to a JSON number. + /// + /// The value to convert. + public static implicit operator JsonNumber(byte value) => new JsonNumber(value); + + /// + /// Converts a to a JSON number. + /// + /// The value to convert. + public static implicit operator JsonNumber(short value) => new JsonNumber(value); + + /// + /// Converts an to a JSON number. + /// + /// The value to convert. + public static implicit operator JsonNumber(int value) => new JsonNumber(value); + + /// + /// Converts a to a JSON number. + /// + /// The value to convert. + public static implicit operator JsonNumber(long value) => new JsonNumber(value); + + /// + /// Converts a to a JSON number. + /// + /// The value to convert. + /// + /// Provided value is not in a legal JSON number format. + /// + public static implicit operator JsonNumber(float value) => new JsonNumber(value); + + /// + /// Converts a to a JSON number. + /// + /// The value to convert. + /// + /// Provided value is not in a legal JSON number format. + /// + public static implicit operator JsonNumber(double value) => new JsonNumber(value); + + /// + /// Converts a to a JSON number. + /// + /// The value to convert. + [CLSCompliant(false)] + public static implicit operator JsonNumber(sbyte value) => new JsonNumber(value); + + /// + /// Converts a to a JSON number. + /// + /// The value to convert. + [CLSCompliant(false)] + public static implicit operator JsonNumber(ushort value) => new JsonNumber(value); + + /// + /// Converts a to a JSON number. + /// + /// The value to convert. + [CLSCompliant(false)] + public static implicit operator JsonNumber(uint value) => new JsonNumber(value); + + /// + /// Converts a to a JSON number. + /// + /// The value to convert. + [CLSCompliant(false)] + public static implicit operator JsonNumber(ulong value) => new JsonNumber(value); + + /// + /// Converts a to a JSON number. + /// + /// The value to convert. + public static implicit operator JsonNumber(decimal value) => new JsonNumber(value); + + /// + /// Compares to the value of this instance. + /// + /// The object to compare against. + /// + /// if the value of this instance matches exactly (is equal and has the same format), + /// otherwise. + /// + public override bool Equals(object obj) => obj is JsonNumber jsonNumber && Equals(jsonNumber); + + /// + /// Calculates a hash code of this instance. + /// + /// A hash code for this instance. + public override int GetHashCode() => _value.GetHashCode(); + + /// + /// Compares other JSON number to the value of this instance. + /// + /// The JSON number to compare against. + /// + /// if the value of this instance matches exactly (is equal and has the same format), + /// otherwise. + /// + public bool Equals(JsonNumber other) => !(other is null) && _value == other._value; + + /// + /// Compares values of two JSON numbers. + /// + /// The JSON number to compare. + /// The JSON number to compare. + /// + /// if values of instances match exactly (are equal and have the same format), + /// otherwise. + /// + public static bool operator ==(JsonNumber left, JsonNumber right) + { + // Test "right" first to allow branch elimination when inlined for null checks (== null) + // so it can become a simple test + if (right is null) + { + // return true/false not the test result https://github.com/dotnet/coreclr/issues/914 + return (left is null) ? true : false; + } + + return right.Equals(left); + } + + /// + /// Compares values of two JSON numbers. + /// + /// The JSON number to compare. + /// The JSON number to compare. + /// + /// if values of instances do not match exactly (are not equal or have different format), + /// otherwise. + /// + public static bool operator !=(JsonNumber left, JsonNumber right) => !(left == right); + } +} diff --git a/src/System.Text.Json/tests/JsonNumberTests.cs b/src/System.Text.Json/tests/JsonNumberTests.cs new file mode 100644 index 000000000000..ed5f9d671ced --- /dev/null +++ b/src/System.Text.Json/tests/JsonNumberTests.cs @@ -0,0 +1,678 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.Globalization; +using Xunit; + +namespace System.Text.Json.Tests +{ + public static class JsonNumberTests + { + private delegate bool TryGetValue(JsonNumber number, out T result); + + private static void TestInitialization( + T value, + Func ctor, + Action setter, + Func getter, + TryGetValue tryGetter, + Func implicitCaster) + { + // Default constructor: + JsonNumber number = new JsonNumber(); + setter(number, value); + AssertValue(value, number, getter, tryGetter); + + // Numeric type constructor: + number = ctor(value); + AssertValue(value, number, getter, tryGetter); + + // String constructor: + number = value switch + { + // Adding CultureInfo.InvariantCulture to support tests on platforms where `,` is a default decimal separator instead of `.` + double doubleValue => new JsonNumber(doubleValue.ToString(CultureInfo.InvariantCulture)), + float floatValue => new JsonNumber(floatValue.ToString(CultureInfo.InvariantCulture)), + decimal decimalValue => new JsonNumber(decimalValue.ToString(CultureInfo.InvariantCulture)), + _ => new JsonNumber(value.ToString()), + }; + AssertValue(value, number, getter, tryGetter); + + // Implicit cast: + number = implicitCaster(value); + AssertValue(value, number, getter, tryGetter); + } + + private static void AssertValue( + T value, + JsonNumber number, + Func getter, + TryGetValue tryGetter) + { + Assert.Equal(value, getter(number)); + Assert.True(tryGetter(number, out T result)); + Assert.Equal(value, result); + } + + [Fact] + public static void TestDefaultCtor() + { + var jsonNumber = new JsonNumber(); + Assert.Equal(0, jsonNumber.GetByte()); + Assert.Equal(0, jsonNumber.GetInt16()); + Assert.Equal(0, jsonNumber.GetInt32()); + Assert.Equal(0, jsonNumber.GetInt64()); + Assert.Equal(0, jsonNumber.GetSingle()); + Assert.Equal(0, jsonNumber.GetDouble()); + Assert.Equal(0, jsonNumber.GetSByte()); + Assert.Equal((ushort)0, jsonNumber.GetUInt16()); + Assert.Equal((uint)0, jsonNumber.GetUInt32()); + Assert.Equal((ulong)0, jsonNumber.GetUInt64()); + Assert.Equal(0, jsonNumber.GetDecimal()); + } + + [Theory] + [InlineData(17)] + [InlineData(byte.MinValue)] + [InlineData(byte.MaxValue)] + public static void TestByte(byte value) + { + TestInitialization( + value, + v => new JsonNumber(v), + (number, v) => number.SetByte(v), + number => number.GetByte(), + (JsonNumber number, out byte v) => number.TryGetByte(out v), + v => v); + } + + [Theory] + [InlineData(0)] + [InlineData(-17)] + [InlineData(17)] + [InlineData(byte.MinValue - 1)] + [InlineData(byte.MaxValue + 1)] + [InlineData(short.MinValue)] + [InlineData(short.MaxValue)] + public static void TestShort(short value) + { + TestInitialization( + value, + v => new JsonNumber(v), + (number, v) => number.SetInt16(v), + number => number.GetInt16(), + (JsonNumber number, out short v) => number.TryGetInt16(out v), + v => v); + } + + [Theory] + [InlineData(0)] + [InlineData(-17)] + [InlineData(17)] + [InlineData(0x2A)] + [InlineData(0b_0110_1010)] + [InlineData(short.MinValue - 1)] + [InlineData(short.MaxValue + 1)] + [InlineData(int.MinValue)] + [InlineData(int.MaxValue)] + public static void TestInt(int value) + { + TestInitialization( + value, + v => new JsonNumber(v), + (number, v) => number.SetInt32(v), + number => number.GetInt32(), + (JsonNumber number, out int v) => number.TryGetInt32(out v), + v => v); + } + + [Theory] + [InlineData(0)] + [InlineData(-17)] + [InlineData(17)] + [InlineData((long)int.MinValue - 1)] + [InlineData((long)int.MaxValue + 1)] + [InlineData(long.MinValue)] + [InlineData(long.MaxValue)] + public static void TestLong(long value) + { + TestInitialization( + value, + v => new JsonNumber(v), + (number, v) => number.SetInt64(v), + number => number.GetInt64(), + (JsonNumber number, out long v) => number.TryGetInt64(out v), + v => v); + } + + [Theory] + [InlineData(0)] + [InlineData(17)] + [InlineData(-17)] + [InlineData(3.14)] + [InlineData(-15.5)] +#if BUILDING_INBOX_LIBRARY + [InlineData(float.MinValue)] + [InlineData(float.MaxValue)] + [InlineData(ulong.MaxValue)] +#endif + public static void TestFloat(float value) + { + TestInitialization( + value, + v => new JsonNumber(v), + (number, v) => number.SetSingle(v), + number => number.GetSingle(), + (JsonNumber number, out float v) => number.TryGetSingle(out v), + v => v); + } + + [Theory] + [InlineData(0)] + [InlineData(17)] + [InlineData(-17)] + [InlineData(3.14)] + [InlineData(-15.5)] +#if BUILDING_INBOX_LIBRARY + [InlineData(float.MinValue)] + [InlineData(float.MaxValue)] + [InlineData(float.MinValue - 1.0)] + [InlineData(float.MaxValue + 1.0)] + [InlineData(double.MinValue)] + [InlineData(double.MaxValue)] + [InlineData(ulong.MaxValue)] +#endif + public static void TestDouble(double value) + { + TestInitialization( + value, + v => new JsonNumber(v), + (number, v) => number.SetDouble(v), + number => number.GetDouble(), + (JsonNumber number, out double v) => number.TryGetDouble(out v), + v => v); + } + + [Theory] + [InlineData(0)] + [InlineData(17)] + [InlineData(-17)] + [InlineData(sbyte.MinValue)] + [InlineData(sbyte.MaxValue)] + public static void TestSByte(sbyte value) + { + TestInitialization( + value, + v => new JsonNumber(v), + (number, v) => number.SetSByte(v), + number => number.GetSByte(), + (JsonNumber number, out sbyte v) => number.TryGetSByte(out v), + v => v); + } + + [Theory] + [InlineData(0)] + [InlineData(17)] + [InlineData(sbyte.MaxValue + 1)] + [InlineData(ushort.MaxValue)] + public static void TestUInt16(ushort value) + { + TestInitialization( + value, + v => new JsonNumber(v), + (number, v) => number.SetUInt16(v), + number => number.GetUInt16(), + (JsonNumber number, out ushort v) => number.TryGetUInt16(out v), + v => v); + } + + [Theory] + [InlineData(0)] + [InlineData(17)] + [InlineData(ushort.MaxValue + 1)] + [InlineData(uint.MaxValue)] + public static void TestUInt32(uint value) + { + TestInitialization( + value, + v => new JsonNumber(v), + (number, v) => number.SetUInt32(v), + number => number.GetUInt32(), + (JsonNumber number, out uint v) => number.TryGetUInt32(out v), + v => v); + } + + [Theory] + [InlineData(0)] + [InlineData(17)] + [InlineData((ulong)uint.MaxValue + 1)] + [InlineData(ulong.MaxValue)] + public static void TestUInt64(ulong value) + { + TestInitialization( + value, + v => new JsonNumber(v), + (number, v) => number.SetUInt64(v), + number => number.GetUInt64(), + (JsonNumber number, out ulong v) => number.TryGetUInt64(out v), + v => v); + } + + public static IEnumerable DecimalData => + new List + { + new object[] { decimal.One }, + new object[] { decimal.Zero }, + new object[] { decimal.MinusOne }, + new object[] { decimal.Divide(1, 2) }, + new object[] { decimal.Divide(1, 3) }, + new object[] { decimal.Divide(1, 10) }, + new object[] { decimal.MinValue }, + new object[] { decimal.MaxValue }, + new object[] { ulong.MaxValue }, + new object[] { ulong.MinValue } + }; + + [Theory] + [MemberData(nameof(DecimalData))] + public static void TestDecimal(decimal value) + { + TestInitialization( + value, + v => new JsonNumber(v), + (number, v) => number.SetDecimal(v), + number => number.GetDecimal(), + (JsonNumber number, out decimal v) => number.TryGetDecimal(out v), + v => v); + } + + [Theory] + [InlineData("0")] + [InlineData("17")] + [InlineData("-456")] + [InlineData("2.3")] + [InlineData("-17.009")] + [InlineData("1e400")] + [InlineData("1e+100000002")] + [InlineData("-79228162514264337593543950336")] + [InlineData("79228162514264337593543950336")] + [InlineData("184467440737095516150.184467440737095516150")] + [InlineData("184467440737095516150184467440737095516150")] + public static void TestString(string value) + { + var jsonNumber = new JsonNumber(value); + Assert.Equal(value, jsonNumber.ToString()); + + jsonNumber = new JsonNumber(); + jsonNumber.SetFormattedValue(value); + Assert.Equal(value, jsonNumber.ToString()); + } + + [Theory] + [InlineData("")] + [InlineData("3,14")] + [InlineData("this is not a number")] + [InlineData("NAN")] + [InlineData("0.")] + [InlineData("008")] + [InlineData("0e")] + [InlineData("5e")] + [InlineData("5a")] + [InlineData("0.1e")] + [InlineData("-01")] + [InlineData("10.5e")] + [InlineData("10.5e-")] + [InlineData("10.5e+")] + [InlineData("10.5e-0.2")] + [InlineData(" 6")] + [InlineData("6 ")] + [InlineData(" 6 ")] + [InlineData("+0")] + [InlineData("+1")] + [InlineData("long.MaxValue")] + [InlineData("3.14f")] + [InlineData("0x2A")] + [InlineData("0b_0110_1010")] + public static void TestInvalidString(string value) + { + Assert.Throws(() => new JsonNumber(value)); + } + + [Fact] + public static void TestNullString() + { + Assert.Throws(() => new JsonNumber(null)); + } + + [Theory] + [InlineData("0")] + [InlineData("0.0")] + [InlineData("-17")] + [InlineData("17")] + [InlineData("3.14")] + [InlineData("1.1e1")] + [InlineData("-3.1415")] + [InlineData("1234567890")] + [InlineData("1e400")] + [InlineData("1e+100000002")] + [InlineData("184467440737095516150.184467440737095516150")] + public static void TestToString(string value) + { + var jsonNumber = new JsonNumber(); + jsonNumber.SetFormattedValue(value); + Assert.Equal(value, jsonNumber.ToString()); + } + + [Fact] + public static void TestUpcasts() + { + byte value = 17; + var jsonNumber = new JsonNumber(value); + + // Getting other types should also succeed: + Assert.Equal(value, jsonNumber.GetInt16()); + Assert.True(jsonNumber.TryGetInt16(out short shortResult)); + Assert.Equal(value, shortResult); + + Assert.Equal(value, jsonNumber.GetInt32()); + Assert.True(jsonNumber.TryGetInt32(out int intResult)); + Assert.Equal(value, intResult); + + Assert.Equal(value, jsonNumber.GetInt64()); + Assert.True(jsonNumber.TryGetInt64(out long longResult)); + Assert.Equal(value, longResult); + + Assert.Equal(value, jsonNumber.GetSingle()); + Assert.True(jsonNumber.TryGetSingle(out float floatResult)); + Assert.Equal(value, floatResult); + + Assert.Equal(value, jsonNumber.GetDouble()); + Assert.True(jsonNumber.TryGetDouble(out double doubleResult)); + Assert.Equal(value, doubleResult); + + Assert.Equal(value, jsonNumber.GetDecimal()); + Assert.True(jsonNumber.TryGetDecimal(out decimal decimalResult)); + Assert.Equal(value, decimalResult); + + Assert.Equal(value, (byte)jsonNumber.GetSByte()); + Assert.True(jsonNumber.TryGetSByte(out sbyte sbyteResult)); + Assert.Equal(value, (byte)sbyteResult); + + Assert.Equal(value, jsonNumber.GetUInt16()); + Assert.True(jsonNumber.TryGetUInt16(out ushort ushortResult)); + Assert.Equal(value, ushortResult); + + Assert.Equal(value, jsonNumber.GetUInt32()); + Assert.True(jsonNumber.TryGetUInt32(out uint uintResult)); + Assert.Equal(value, uintResult); + + Assert.Equal(value, jsonNumber.GetUInt64()); + Assert.True(jsonNumber.TryGetUInt64(out ulong ulongResult)); + Assert.Equal(value, ulongResult); + } + + [Fact] + public static void TestIntegerGetMismatches() + { + var jsonNumber = new JsonNumber(long.MaxValue); + + // Getting smaller types should fail: + Assert.False(jsonNumber.TryGetByte(out byte byteResult)); + Assert.Throws(() => jsonNumber.GetByte()); + + Assert.False(jsonNumber.TryGetInt16(out short shortResult)); + Assert.Throws(() => jsonNumber.GetInt16()); + + Assert.False(jsonNumber.TryGetInt32(out int intResult)); + Assert.Throws(() => jsonNumber.GetInt32()); + + Assert.False(jsonNumber.TryGetSByte(out sbyte sbyteResult)); + Assert.Throws(() => jsonNumber.GetSByte()); + + Assert.False(jsonNumber.TryGetUInt16(out ushort ushortResult)); + Assert.Throws(() => jsonNumber.GetUInt16()); + + Assert.False(jsonNumber.TryGetUInt32(out uint uintResult)); + Assert.Throws(() => jsonNumber.GetUInt32()); + } + + [Fact] + public static void TestUnsignedGetMismatches() + { + var jsonNumber = new JsonNumber("-1"); + + // Getting unsigned types should fail: + Assert.False(jsonNumber.TryGetByte(out byte byteResult)); + Assert.Throws(() => jsonNumber.GetByte()); + + Assert.False(jsonNumber.TryGetUInt16(out ushort ushortResult)); + Assert.Throws(() => jsonNumber.GetUInt16()); + + Assert.False(jsonNumber.TryGetUInt32(out uint uintResult)); + Assert.Throws(() => jsonNumber.GetUInt32()); + + Assert.False(jsonNumber.TryGetUInt64(out ulong ulongResult)); + Assert.Throws(() => jsonNumber.GetUInt64()); + } + + [Fact] + public static void TestRationalGetMismatches() + { + var jsonNumber = new JsonNumber("3.14"); + + // Getting integer types should fail: + Assert.False(jsonNumber.TryGetByte(out byte byteResult)); + Assert.Throws(() => jsonNumber.GetByte()); + + Assert.False(jsonNumber.TryGetInt16(out short shortResult)); + Assert.Throws(() => jsonNumber.GetInt16()); + + Assert.False(jsonNumber.TryGetInt32(out int intResult)); + Assert.Throws(() => jsonNumber.GetInt32()); + + Assert.False(jsonNumber.TryGetInt64(out long longResult)); + Assert.Throws(() => jsonNumber.GetInt64()); + + Assert.False(jsonNumber.TryGetSByte(out sbyte sbyteResult)); + Assert.Throws(() => jsonNumber.GetSByte()); + + Assert.False(jsonNumber.TryGetUInt16(out ushort ushortResult)); + Assert.Throws(() => jsonNumber.GetUInt16()); + + Assert.False(jsonNumber.TryGetUInt32(out uint uintResult)); + Assert.Throws(() => jsonNumber.GetUInt32()); + + Assert.False(jsonNumber.TryGetUInt64(out ulong ulongResult)); + Assert.Throws(() => jsonNumber.GetUInt64()); + + jsonNumber = new JsonNumber(double.MaxValue); + + if (PlatformDetection.IsFullFramework) + { + // Full framework throws for overflow rather than returning Infinity + // This was fixed for .NET Core 3.0 in order to be IEEE 754 compliant + Assert.Throws(() => jsonNumber.GetSingle()); + // Getting double fails as well + Assert.Throws(() => jsonNumber.GetDouble()); + } + else + { + Assert.Equal(float.PositiveInfinity, jsonNumber.GetSingle()); + } + Assert.Throws(() => jsonNumber.GetDecimal()); + + jsonNumber = new JsonNumber("5e500"); + + if (PlatformDetection.IsFullFramework) + { + Assert.Throws(() => jsonNumber.GetSingle()); + Assert.Throws(() => jsonNumber.GetDouble()); + } + else + { + Assert.Equal(float.PositiveInfinity, jsonNumber.GetSingle()); + Assert.Equal(double.PositiveInfinity, jsonNumber.GetDouble()); + } + + Assert.Throws(() => jsonNumber.GetDecimal()); + } + + [InlineData(float.PositiveInfinity)] + [InlineData(float.NegativeInfinity)] + [Theory] + public static void TestFloatInfinities(float value) + { + Assert.Throws(() => new JsonNumber(value)); + } + + [InlineData(double.PositiveInfinity)] + [InlineData(double.NegativeInfinity)] + [Theory] + public static void TestDoubleIninities(double value) + { + Assert.Throws (() => new JsonNumber(value)); + } + + [Fact] + public static void TestScientificNotation() + { + var jsonNumber = new JsonNumber("5e6"); + Assert.Equal(5000000f, jsonNumber.GetSingle()); + Assert.Equal(5000000, jsonNumber.GetDouble()); + + jsonNumber = new JsonNumber("3.14e0"); + Assert.Equal(3.14f, jsonNumber.GetSingle()); + Assert.Equal(3.14, jsonNumber.GetDouble()); + + jsonNumber = new JsonNumber("7e-3"); + Assert.Equal(0.007f, jsonNumber.GetSingle()); + Assert.Equal(0.007, jsonNumber.GetDouble()); + + jsonNumber = new JsonNumber("-7e-3"); + Assert.Equal(-0.007f, jsonNumber.GetSingle()); + Assert.Equal(-0.007, jsonNumber.GetDouble()); + } + + + [Fact] + public static void TestChangingTypes() + { + var jsonNumber = new JsonNumber(5); + Assert.Equal(5, jsonNumber.GetInt32()); + + jsonNumber.SetDouble(3.14); + Assert.Equal(3.14, jsonNumber.GetDouble()); + + jsonNumber.SetByte(17); + Assert.Equal(17, jsonNumber.GetByte()); + + jsonNumber.SetInt64(long.MaxValue); + Assert.Equal(long.MaxValue, jsonNumber.GetInt64()); + + jsonNumber.SetUInt16(ushort.MaxValue); + Assert.Equal(ushort.MaxValue, jsonNumber.GetUInt16()); + + jsonNumber.SetSingle(-1.1f); + Assert.Equal(-1.1f, jsonNumber.GetSingle()); + + jsonNumber.SetSByte(4); + Assert.Equal(4, jsonNumber.GetSByte()); + + jsonNumber.SetUInt32(127); + Assert.Equal((uint)127, jsonNumber.GetUInt32()); + + jsonNumber.SetFormattedValue("1e400"); + Assert.Equal("1e400", jsonNumber.ToString()); + + jsonNumber.SetUInt64(ulong.MaxValue); + Assert.Equal(ulong.MaxValue, jsonNumber.GetUInt64()); + + jsonNumber.SetDecimal(decimal.MaxValue); + Assert.Equal(decimal.MaxValue, jsonNumber.GetDecimal()); + } + + [Fact] + public static void TestEquals() + { + var jsonNumber = new JsonNumber(123); + + Assert.True(jsonNumber.Equals(new JsonNumber(123))); + Assert.True(new JsonNumber(123).Equals(jsonNumber)); + + Assert.True(jsonNumber.Equals(new JsonNumber((ushort)123))); + Assert.True(new JsonNumber((ushort)123).Equals(jsonNumber)); + + Assert.True(jsonNumber.Equals(new JsonNumber("123"))); + Assert.True(new JsonNumber("123").Equals(jsonNumber)); + + Assert.False(jsonNumber.Equals(new JsonNumber("123e0"))); + Assert.False(new JsonNumber("123e0").Equals(jsonNumber)); + + Assert.False(jsonNumber.Equals(new JsonNumber("123e1"))); + Assert.False(new JsonNumber("123e1").Equals(jsonNumber)); + + Assert.False(jsonNumber.Equals(new JsonNumber(17))); + Assert.False(new JsonNumber(17).Equals(jsonNumber)); + + Assert.True(jsonNumber == new JsonNumber(123)); + Assert.True(jsonNumber != new JsonNumber(17)); + + JsonNode jsonNode = new JsonNumber(123); + Assert.True(jsonNumber.Equals(jsonNode)); + + IEquatable jsonNumberIEquatable = jsonNumber; + Assert.True(jsonNumberIEquatable.Equals(jsonNumber)); + Assert.True(jsonNumber.Equals(jsonNumberIEquatable)); + + Assert.False(jsonNumber.Equals(null)); + + object jsonNumberCopy = jsonNumber; + object jsonNumberObject = new JsonNumber(123); + Assert.True(jsonNumber.Equals(jsonNumberObject)); + Assert.True(jsonNumberCopy.Equals(jsonNumberObject)); + Assert.True(jsonNumberObject.Equals(jsonNumber)); + + jsonNumber = new JsonNumber(); + Assert.True(jsonNumber.Equals(new JsonNumber())); + Assert.False(jsonNumber.Equals(new JsonNumber(5))); + + Assert.False(jsonNumber.Equals(new Exception())); + + JsonNumber jsonNumberNull = null; + Assert.False(jsonNumber == jsonNumberNull); + Assert.False(jsonNumberNull == jsonNumber); + + Assert.True(jsonNumber != jsonNumberNull); + Assert.True(jsonNumberNull != jsonNumber); + } + + [Fact] + public static void TestGetHashCode() + { + var jsonNumber = new JsonNumber(123); + + Assert.Equal(jsonNumber.GetHashCode(), new JsonNumber(123).GetHashCode()); + Assert.Equal(jsonNumber.GetHashCode(), new JsonNumber((ushort)123).GetHashCode()); + Assert.Equal(jsonNumber.GetHashCode(), new JsonNumber("123").GetHashCode()); + Assert.NotEqual(jsonNumber.GetHashCode(), new JsonNumber("123e0").GetHashCode()); + Assert.NotEqual(jsonNumber.GetHashCode(), new JsonNumber("123e1").GetHashCode()); + Assert.NotEqual(jsonNumber.GetHashCode(), new JsonNumber(17).GetHashCode()); + + JsonNode jsonNode = new JsonNumber(123); + Assert.Equal(jsonNumber.GetHashCode(), jsonNode.GetHashCode()); + + IEquatable jsonNumberIEquatable = jsonNumber; + Assert.Equal(jsonNumber.GetHashCode(), jsonNumberIEquatable.GetHashCode()); + + object jsonNumberCopy = jsonNumber; + object jsonNumberObject = new JsonNumber(17); + + Assert.Equal(jsonNumber.GetHashCode(), jsonNumberCopy.GetHashCode()); + Assert.NotEqual(jsonNumber.GetHashCode(), jsonNumberObject.GetHashCode()); + + Assert.Equal(new JsonNumber().GetHashCode(), new JsonNumber().GetHashCode()); + } + } +} diff --git a/src/System.Text.Json/tests/System.Text.Json.Tests.csproj b/src/System.Text.Json/tests/System.Text.Json.Tests.csproj index c60ca78db8fe..a76a4fcabc0c 100644 --- a/src/System.Text.Json/tests/System.Text.Json.Tests.csproj +++ b/src/System.Text.Json/tests/System.Text.Json.Tests.csproj @@ -110,6 +110,9 @@ CommonTest\System\Buffers\ArrayBufferWriter.cs + + +