diff --git a/Src/Newtonsoft.Json.Tests/Linq/DynamicTests.cs b/Src/Newtonsoft.Json.Tests/Linq/DynamicTests.cs index 02b23311..fb179ccc 100644 --- a/Src/Newtonsoft.Json.Tests/Linq/DynamicTests.cs +++ b/Src/Newtonsoft.Json.Tests/Linq/DynamicTests.cs @@ -107,31 +107,66 @@ namespace Newtonsoft.Json.Tests.Linq Assert.AreEqual("blah blah", (string)v); } - //[Test] - //public void JValueEquals() - //{ - // JObject o = new JObject( - // new JProperty("Null", new JValue(null, JTokenType.Null)), - // new JProperty("Integer", new JValue(1)), - // new JProperty("Float", new JValue(1.1)), - // new JProperty("DateTime", new JValue(new DateTime(2000, 12, 29, 23, 51, 10, DateTimeKind.Utc))), - // new JProperty("Boolean", new JValue(true)), - // new JProperty("String", new JValue("A string lol!")), - // new JProperty("Bytes", new JValue(Encoding.UTF8.GetBytes("A string lol!"))) - // ); + [Test] + public void JValueEquals() + { + JObject o = new JObject( + new JProperty("Null", new JValue(null, JTokenType.Null)), + new JProperty("Integer", new JValue(1)), + new JProperty("Float", new JValue(1.1d)), + new JProperty("Decimal", new JValue(1.1m)), + new JProperty("DateTime", new JValue(new DateTime(2000, 12, 29, 23, 51, 10, DateTimeKind.Utc))), + new JProperty("Boolean", new JValue(true)), + new JProperty("String", new JValue("A string lol!")), + new JProperty("Bytes", new JValue(Encoding.UTF8.GetBytes("A string lol!"))) + ); - // dynamic d = o; + dynamic d = o; - // Assert.IsTrue(d.Null == null); - // Assert.IsTrue(d.Null == new JValue(null, JTokenType.Null)); + Assert.IsTrue(d.Null == d.Null); + Assert.IsTrue(d.Null == null); + Assert.IsTrue(d.Null == new JValue(null, JTokenType.Null)); + Assert.IsFalse(d.Null == 1); - // //if (d.NullValue == null) - // // Console.WriteLine("Null"); - // //else - // // Console.WriteLine("Not null"); + Assert.IsTrue(d.Integer == d.Integer); + Assert.IsTrue(d.Integer > 0); + Assert.IsTrue(d.Integer > 0.0m); + Assert.IsTrue(d.Integer > 0.0f); + Assert.IsTrue(d.Integer > null); + Assert.IsTrue(d.Integer >= null); + Assert.IsTrue(d.Integer == 1); + Assert.IsTrue(d.Integer == 1m); + Assert.IsTrue(d.Integer != 1.1f); + Assert.IsTrue(d.Integer != 1.1d); - // //Console.WriteLine(d.IntValue == 1); - //} + Assert.IsTrue(d.Decimal == d.Decimal); + Assert.IsTrue(d.Decimal > 0); + Assert.IsTrue(d.Decimal > 0.0m); + Assert.IsTrue(d.Decimal > 0.0f); + Assert.IsTrue(d.Decimal > null); + Assert.IsTrue(d.Decimal >= null); + Assert.IsTrue(d.Decimal == 1.1); + Assert.IsTrue(d.Decimal == 1.1m); + Assert.IsTrue(d.Decimal != 1.0f); + Assert.IsTrue(d.Decimal != 1.0d); + + Assert.IsTrue(d.Float == d.Float); + Assert.IsTrue(d.Float > 0); + Assert.IsTrue(d.Float > 0.0m); + Assert.IsTrue(d.Float > 0.0f); + Assert.IsTrue(d.Float > null); + Assert.IsTrue(d.Float >= null); + Assert.IsTrue(d.Float < 2); + Assert.IsTrue(d.Float <= 1.1); + Assert.IsTrue(d.Float == 1.1); + Assert.IsTrue(d.Float == 1.1m); + Assert.IsTrue(d.Float != 1.0f); + Assert.IsTrue(d.Float != 1.0d); + + Assert.IsTrue(d.Bytes == d.Bytes); + Assert.IsTrue(d.Bytes == Encoding.UTF8.GetBytes("A string lol!")); + Assert.IsTrue(d.Bytes == new JValue(Encoding.UTF8.GetBytes("A string lol!"))); + } [Test] public void JValueToString() @@ -191,6 +226,8 @@ namespace Newtonsoft.Json.Tests.Linq AssertValueConverted(true); AssertValueConverted(true); AssertValueConverted(false); + AssertValueConverted(null); + AssertValueConverted("true", true); AssertValueConverted(null); AssertValueConverted(Encoding.UTF8.GetBytes("blah")); AssertValueConverted(new DateTime(2000, 12, 20, 23, 59, 2, DateTimeKind.Utc)); @@ -201,7 +238,11 @@ namespace Newtonsoft.Json.Tests.Linq AssertValueConverted(null); AssertValueConverted(99.9m); AssertValueConverted(99.9m); + AssertValueConverted(1); + AssertValueConverted(1.1f, 1.1m); + AssertValueConverted("1.1", 1.1m); AssertValueConverted(99.9); + AssertValueConverted(99.9m); AssertValueConverted(99.9); AssertValueConverted(99.9f); AssertValueConverted(99.9f); @@ -212,8 +253,11 @@ namespace Newtonsoft.Json.Tests.Linq AssertValueConverted(short.MaxValue); AssertValueConverted(short.MaxValue); AssertValueConverted("blah"); + AssertValueConverted(null); + AssertValueConverted(1, "1"); AssertValueConverted(uint.MinValue); AssertValueConverted(uint.MinValue); + AssertValueConverted("1", 1); AssertValueConverted(ulong.MaxValue); AssertValueConverted(ulong.MaxValue); AssertValueConverted(ushort.MinValue); @@ -221,13 +265,18 @@ namespace Newtonsoft.Json.Tests.Linq AssertValueConverted(null); } - private static void AssertValueConverted(T value) + private static void AssertValueConverted(object value) + { + AssertValueConverted(value, value); + } + + private static void AssertValueConverted(object value, object expected) { JValue v = new JValue(value); dynamic d = v; T t = d; - Assert.AreEqual(value, t); + Assert.AreEqual(expected, t); } [Test] diff --git a/Src/Newtonsoft.Json.Tests/Linq/JValueTests.cs b/Src/Newtonsoft.Json.Tests/Linq/JValueTests.cs index 0550ca8c..253b3418 100644 --- a/Src/Newtonsoft.Json.Tests/Linq/JValueTests.cs +++ b/Src/Newtonsoft.Json.Tests/Linq/JValueTests.cs @@ -222,5 +222,21 @@ namespace Newtonsoft.Json.Tests.Linq IComparable c = (new JValue(1).Value()); Assert.AreEqual(1, c); } + + [Test] + public void Ordering() + { + JObject o = new JObject( + new JProperty("Integer", new JValue(1)), + new JProperty("Float", new JValue(1.2d)), + new JProperty("Decimal", new JValue(1.1m)) + ); + + IList orderedValues = o.Values().Cast().OrderBy(v => v).Select(v => v.Value).ToList(); + + Assert.AreEqual(1, orderedValues[0]); + Assert.AreEqual(1.1, orderedValues[1]); + Assert.AreEqual(1.2, orderedValues[2]); + } } } \ No newline at end of file diff --git a/Src/Newtonsoft.Json/Linq/JValue.cs b/Src/Newtonsoft.Json/Linq/JValue.cs index 8bbb3216..7da76786 100644 --- a/Src/Newtonsoft.Json/Linq/JValue.cs +++ b/Src/Newtonsoft.Json/Linq/JValue.cs @@ -40,7 +40,7 @@ namespace Newtonsoft.Json.Linq /// /// Represents a value in JSON (string, integer, date, etc). /// - public class JValue : JToken, IEquatable, IFormattable + public class JValue : JToken, IEquatable, IFormattable, IComparable, IComparable { private JTokenType _valueType; private object _value; @@ -144,47 +144,85 @@ namespace Newtonsoft.Json.Linq get { return false; } } - private static bool Compare(JTokenType valueType, object objA, object objB) + private static int Compare(JTokenType valueType, object objA, object objB) { if (objA == null && objB == null) - return true; - if (objA == null || objB == null) - return false; + return 0; + if (objA != null && objB == null) + return 1; + if (objA == null && objB != null) + return -1; switch (valueType) { case JTokenType.Integer: - if (objA is ulong || objB is ulong) - return Convert.ToDecimal(objA, CultureInfo.InvariantCulture).Equals(Convert.ToDecimal(objB, CultureInfo.InvariantCulture)); + if (objA is ulong || objB is ulong || objA is decimal || objB is decimal) + return Convert.ToDecimal(objA, CultureInfo.InvariantCulture).CompareTo(Convert.ToDecimal(objB, CultureInfo.InvariantCulture)); + else if (objA is float || objB is float || objA is double || objB is double) + return CompareFloat(objA, objB); else - return Convert.ToInt64(objA, CultureInfo.InvariantCulture).Equals(Convert.ToInt64(objB, CultureInfo.InvariantCulture)); + return Convert.ToInt64(objA, CultureInfo.InvariantCulture).CompareTo(Convert.ToInt64(objB, CultureInfo.InvariantCulture)); case JTokenType.Float: - double d1 = Convert.ToDouble(objA, CultureInfo.InvariantCulture); - double d2 = Convert.ToDouble(objB, CultureInfo.InvariantCulture); - if (d1.Equals(d2)) - return true; - - // take into account possible floating point errors - return MathUtils.ApproxEquals(d1, d2); + return CompareFloat(objA, objB); case JTokenType.Comment: case JTokenType.String: - case JTokenType.Boolean: case JTokenType.Raw: - return objA.Equals(objB); - case JTokenType.Date: - return objA.Equals(objB); - case JTokenType.Bytes: - byte[] b1 = objA as byte[]; - byte[] b2 = objB as byte[]; - if (b1 == null || b2 == null) - return false; + string s1 = Convert.ToString(objA); + string s2 = Convert.ToString(objB); - return MiscellaneousUtils.ByteArrayCompare(b1, b2); + return s1.CompareTo(s2); + case JTokenType.Boolean: + bool b1 = Convert.ToBoolean(objA); + bool b2 = Convert.ToBoolean(objB); + + return b1.CompareTo(b2); + case JTokenType.Date: + if (objA is DateTime) + { + DateTime date1 = Convert.ToDateTime(objA); + DateTime date2 = Convert.ToDateTime(objB); + + return date1.CompareTo(date2); + } + else + { + if (!(objB is DateTimeOffset)) + throw new ArgumentException("Object must be of type DateTimeOffset."); + + DateTimeOffset date1 = (DateTimeOffset)objA; + DateTimeOffset date2 = (DateTimeOffset)objB; + + return date1.CompareTo(date2); + } + case JTokenType.Bytes: + if (!(objB is byte[])) + throw new ArgumentException("Object must be of type byte[]."); + + byte[] bytes1 = objA as byte[]; + byte[] bytes2 = objB as byte[]; + if (bytes1 == null) + return -1; + if (bytes2 == null) + return 1; + + return MiscellaneousUtils.ByteArrayCompare(bytes1, bytes2); default: throw MiscellaneousUtils.CreateArgumentOutOfRangeException("valueType", valueType, "Unexpected value type: {0}".FormatWith(CultureInfo.InvariantCulture, valueType)); } } + private static int CompareFloat(object objA, object objB) + { + double d1 = Convert.ToDouble(objA, CultureInfo.InvariantCulture); + double d2 = Convert.ToDouble(objB, CultureInfo.InvariantCulture); + + // take into account possible floating point errors + if (MathUtils.ApproxEquals(d1, d2)) + return 0; + + return d1.CompareTo(d2); + } + internal override JToken CloneToken() { return new JValue(this); @@ -352,7 +390,7 @@ namespace Newtonsoft.Json.Linq private static bool ValuesEquals(JValue v1, JValue v2) { - return (v1 == v2 || (v1._valueType == v2._valueType && Compare(v1._valueType, v1._value, v2._value))); + return (v1 == v2 || (v1._valueType == v2._valueType && Compare(v1._valueType, v1._value, v2._value) == 0)); } /// @@ -475,26 +513,54 @@ namespace Newtonsoft.Json.Linq return true; } - //public override bool TryBinaryOperation(JValue instance, BinaryOperationBinder binder, object arg, out object result) - //{ - // dynamic d1 = arg; - // dynamic d2 = instance._value; - // //// use built in JValue equals - // //if (arg is JValue) - // //{ - // // result = null; - // // return false; - // //} + public override bool TryBinaryOperation(JValue instance, BinaryOperationBinder binder, object arg, out object result) + { + object compareValue = (arg is JValue) ? ((JValue) arg).Value : arg; - // //if (binder.Operation == ExpressionType.Equal) - // //{ - // // result = Compare(instance.Type, instance.Value, arg); - // // return true; - // //} - - // //return base.TryBinaryOperation(instance, binder, arg, out result); - //} + switch (binder.Operation) + { + case ExpressionType.Equal: + result = (Compare(instance.Type, instance.Value, compareValue) == 0); + return true; + case ExpressionType.NotEqual: + result = (Compare(instance.Type, instance.Value, compareValue) != 0); + return true; + case ExpressionType.GreaterThan: + result = (Compare(instance.Type, instance.Value, compareValue) > 0); + return true; + case ExpressionType.GreaterThanOrEqual: + result = (Compare(instance.Type, instance.Value, compareValue) >= 0); + return true; + case ExpressionType.LessThan: + result = (Compare(instance.Type, instance.Value, compareValue) < 0); + return true; + case ExpressionType.LessThanOrEqual: + result = (Compare(instance.Type, instance.Value, compareValue) <= 0); + return true; + } + + result = null; + return false; + } } #endif + + int IComparable.CompareTo(object obj) + { + if (obj == null) + return 1; + + object otherValue = (obj is JValue) ? ((JValue) obj).Value : obj; + + return Compare(_valueType, _value, otherValue); + } + + public int CompareTo(JValue other) + { + if (other == null) + return 1; + + return Compare(_valueType, _value, other._value); + } } } \ No newline at end of file diff --git a/Src/Newtonsoft.Json/Utilities/MathUtils.cs b/Src/Newtonsoft.Json/Utilities/MathUtils.cs index e60ed056..1059686e 100644 --- a/Src/Newtonsoft.Json/Utilities/MathUtils.cs +++ b/Src/Newtonsoft.Json/Utilities/MathUtils.cs @@ -127,8 +127,8 @@ namespace Newtonsoft.Json.Utilities public static bool ApproxEquals(double d1, double d2) { - // are values equal to within 12 (or so) digits of precision? - return Math.Abs(d1 - d2) < (Math.Abs(d1) * 1e-12); + // are values equal to within 6 (or so) digits of precision? + return Math.Abs(d1 - d2) < (Math.Abs(d1) * 1e-6); } } } \ No newline at end of file diff --git a/Src/Newtonsoft.Json/Utilities/MiscellaneousUtils.cs b/Src/Newtonsoft.Json/Utilities/MiscellaneousUtils.cs index b6665c92..f0fa2dda 100644 --- a/Src/Newtonsoft.Json/Utilities/MiscellaneousUtils.cs +++ b/Src/Newtonsoft.Json/Utilities/MiscellaneousUtils.cs @@ -85,18 +85,20 @@ namespace Newtonsoft.Json.Utilities return hex; } - public static bool ByteArrayCompare(byte[] a1, byte[] a2) + public static int ByteArrayCompare(byte[] a1, byte[] a2) { - if (a1.Length != a2.Length) - return false; + int lengthCompare = a1.Length.CompareTo(a2.Length); + if (lengthCompare != 0) + return lengthCompare; for (int i = 0; i < a1.Length; i++) { - if (a1[i] != a2[i]) - return false; + int valueCompare = a1[i].CompareTo(a2[i]); + if (valueCompare != 0) + return valueCompare; } - return true; + return 0; } public static string GetPrefix(string qualifiedName)