From a8249e38df8c3f55cf3f22585222f245741e05d5 Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Sun, 18 Nov 2012 21:10:01 +1300 Subject: [PATCH] -Added non-generic JToken.ToObject methods -Added byte, char, Guid, TimeSpan and Uri explicit conversion operators to JToken -Fixed globalization warnings --- Src/Newtonsoft.Json.Tests/Linq/JTokenTests.cs | 49 ++- Src/Newtonsoft.Json/Linq/JToken.cs | 294 +++++++++++++++++- Src/Newtonsoft.Json/Linq/JValue.cs | 9 + .../Serialization/MemoryTraceWriter.cs | 3 +- Src/Newtonsoft.Json/Utilities/ConvertUtils.cs | 17 +- 5 files changed, 357 insertions(+), 15 deletions(-) diff --git a/Src/Newtonsoft.Json.Tests/Linq/JTokenTests.cs b/Src/Newtonsoft.Json.Tests/Linq/JTokenTests.cs index ebd327db..96bb225e 100644 --- a/Src/Newtonsoft.Json.Tests/Linq/JTokenTests.cs +++ b/Src/Newtonsoft.Json.Tests/Linq/JTokenTests.cs @@ -230,7 +230,7 @@ namespace Newtonsoft.Json.Tests.Linq public void Casting() { Assert.AreEqual(1L, (long)(new JValue(1))); - Assert.AreEqual(2L, (long) new JArray(1, 2, 3)[1]); + Assert.AreEqual(2L, (long)new JArray(1, 2, 3)[1]); Assert.AreEqual(new DateTime(2000, 12, 20), (DateTime)new JValue(new DateTime(2000, 12, 20))); #if !PocketPC && !NET20 @@ -261,6 +261,8 @@ namespace Newtonsoft.Json.Tests.Linq Assert.AreEqual(null, (uint?)(JValue)null); Assert.AreEqual(null, (sbyte?)new JValue((sbyte?)null)); Assert.AreEqual(null, (sbyte?)(JValue)null); + Assert.AreEqual(null, (byte?)new JValue((byte?)null)); + Assert.AreEqual(null, (byte?)(JValue)null); Assert.AreEqual(null, (ulong?)new JValue((ulong?)null)); Assert.AreEqual(null, (ulong?)(JValue)null); Assert.AreEqual(null, (uint?)new JValue((uint?)null)); @@ -277,6 +279,7 @@ namespace Newtonsoft.Json.Tests.Linq Assert.AreEqual(5f, (float)(new JValue(5L))); Assert.AreEqual(5f, (float)(new JValue(5m))); Assert.AreEqual(5f, (float?)(new JValue(5m))); + Assert.AreEqual(5, (byte)(new JValue(5))); byte[] data = new byte[0]; Assert.AreEqual(data, (byte[])(new JValue(data))); @@ -284,6 +287,49 @@ namespace Newtonsoft.Json.Tests.Linq Assert.AreEqual(5, (int)(new JValue(StringComparison.OrdinalIgnoreCase))); } + [Test] + public void ToObject() + { + Assert.AreEqual(1L, (new JValue(1).ToObject(typeof(ushort)))); + Assert.AreEqual(1L, (new JValue(1).ToObject(typeof(ushort?)))); + Assert.AreEqual(1L, (new JValue(1).ToObject(typeof(uint)))); + Assert.AreEqual(1L, (new JValue(1).ToObject(typeof(uint?)))); + Assert.AreEqual(1L, (new JValue(1).ToObject(typeof(ulong)))); + Assert.AreEqual(1L, (new JValue(1).ToObject(typeof(ulong?)))); + Assert.AreEqual(1L, (new JValue(1).ToObject(typeof(sbyte)))); + Assert.AreEqual(1L, (new JValue(1).ToObject(typeof(sbyte?)))); + Assert.AreEqual(1L, (new JValue(1).ToObject(typeof(byte)))); + Assert.AreEqual(1L, (new JValue(1).ToObject(typeof(byte?)))); + Assert.AreEqual(1L, (new JValue(1).ToObject(typeof(short)))); + Assert.AreEqual(1L, (new JValue(1).ToObject(typeof(short?)))); + Assert.AreEqual(1L, (new JValue(1).ToObject(typeof(int)))); + Assert.AreEqual(1L, (new JValue(1).ToObject(typeof(int?)))); + Assert.AreEqual(1L, (new JValue(1).ToObject(typeof(long)))); + Assert.AreEqual(1L, (new JValue(1).ToObject(typeof(long?)))); + Assert.AreEqual(1L, (new JValue(1.0).ToObject(typeof(float)))); + Assert.AreEqual(1L, (new JValue(1.0).ToObject(typeof(float?)))); + Assert.AreEqual(1L, (new JValue(1.0).ToObject(typeof(double)))); + Assert.AreEqual(1L, (new JValue(1.0).ToObject(typeof(double?)))); + Assert.AreEqual(1L, (new JValue(1).ToObject(typeof(decimal)))); + Assert.AreEqual(1L, (new JValue(1).ToObject(typeof(decimal?)))); + Assert.AreEqual(true, (new JValue(true).ToObject(typeof(bool)))); + Assert.AreEqual(true, (new JValue(true).ToObject(typeof(bool?)))); + Assert.AreEqual('b', (new JValue('b').ToObject(typeof(char)))); + Assert.AreEqual('b', (new JValue('b').ToObject(typeof(char?)))); + Assert.AreEqual(TimeSpan.MaxValue, (new JValue(TimeSpan.MaxValue).ToObject(typeof(TimeSpan)))); + Assert.AreEqual(TimeSpan.MaxValue, (new JValue(TimeSpan.MaxValue).ToObject(typeof(TimeSpan?)))); + Assert.AreEqual(DateTime.MaxValue, (new JValue(DateTime.MaxValue).ToObject(typeof(DateTime)))); + Assert.AreEqual(DateTime.MaxValue, (new JValue(DateTime.MaxValue).ToObject(typeof(DateTime?)))); +#if !NET20 + Assert.AreEqual(DateTimeOffset.MaxValue, (new JValue(DateTimeOffset.MaxValue).ToObject(typeof(DateTimeOffset)))); + Assert.AreEqual(DateTimeOffset.MaxValue, (new JValue(DateTimeOffset.MaxValue).ToObject(typeof(DateTimeOffset?)))); +#endif + Assert.AreEqual("b", (new JValue("b").ToObject(typeof(string)))); + Assert.AreEqual(new Guid("A34B2080-B5F0-488E-834D-45D44ECB9E5C"), (new JValue(new Guid("A34B2080-B5F0-488E-834D-45D44ECB9E5C")).ToObject(typeof(Guid)))); + Assert.AreEqual(new Guid("A34B2080-B5F0-488E-834D-45D44ECB9E5C"), (new JValue(new Guid("A34B2080-B5F0-488E-834D-45D44ECB9E5C")).ToObject(typeof(Guid?)))); + Assert.AreEqual(new Uri("http://www.google.com/"), (new JValue(new Uri("http://www.google.com/")).ToObject(typeof(Uri)))); + } + [Test] public void ImplicitCastingTo() { @@ -308,6 +354,7 @@ namespace Newtonsoft.Json.Tests.Linq Assert.IsTrue(JToken.DeepEquals(new JValue((ulong?)null), (JValue)(ulong?)null)); Assert.IsTrue(JToken.DeepEquals(new JValue((sbyte?)null), (JValue)(sbyte?)null)); Assert.IsTrue(JToken.DeepEquals(new JValue((ushort?)null), (JValue)(ushort?)null)); + Assert.IsTrue(JToken.DeepEquals(new JValue(short.MaxValue), (JValue)short.MaxValue)); Assert.IsTrue(JToken.DeepEquals(new JValue(ushort.MaxValue), (JValue)ushort.MaxValue)); Assert.IsTrue(JToken.DeepEquals(new JValue(11.1f), (JValue)11.1f)); Assert.IsTrue(JToken.DeepEquals(new JValue(float.MinValue), (JValue)float.MinValue)); diff --git a/Src/Newtonsoft.Json/Linq/JToken.cs b/Src/Newtonsoft.Json/Linq/JToken.cs index 3ef15546..3301fc49 100644 --- a/Src/Newtonsoft.Json/Linq/JToken.cs +++ b/Src/Newtonsoft.Json/Linq/JToken.cs @@ -403,9 +403,9 @@ namespace Newtonsoft.Json.Linq return (o.Type == JTokenType.Boolean || (nullable && IsNullable(o))); } - private static bool ValidateString(JToken o) + private static bool ValidateString(JToken o, bool nullable) { - return (o.Type == JTokenType.String || o.Type == JTokenType.Comment || o.Type == JTokenType.Raw || IsNullable(o)); + return (o.Type == JTokenType.String || o.Type == JTokenType.Comment || o.Type == JTokenType.Raw || (nullable && IsNullable(o))); } private static bool ValidateBytes(JToken o) @@ -542,7 +542,24 @@ namespace Newtonsoft.Json.Linq if (v == null || !ValidateFloat(v, true)) throw new ArgumentException("Can not convert {0} to Double.".FormatWith(CultureInfo.InvariantCulture, GetType(value))); - return (double?)v.Value; + return (v.Value != null) ? (double?)Convert.ToDouble(v.Value, CultureInfo.InvariantCulture) : null; + } + + /// + /// Performs an explicit conversion from to . + /// + /// The value. + /// The result of the conversion. + public static explicit operator char?(JToken value) + { + if (value == null) + return null; + + JValue v = EnsureValue(value); + if (v == null || !ValidateString(v, true)) + throw new ArgumentException("Can not convert {0} to Char.".FormatWith(CultureInfo.InvariantCulture, GetType(value))); + + return (v.Value != null) ? (char?)Convert.ToChar(v.Value, CultureInfo.InvariantCulture) : null; } /// @@ -588,6 +605,35 @@ namespace Newtonsoft.Json.Linq return Convert.ToUInt16(v.Value, CultureInfo.InvariantCulture); } + /// + /// Performs an explicit conversion from to . + /// + /// The value. + /// The result of the conversion. + [CLSCompliant(false)] + public static explicit operator char(JToken value) + { + JValue v = EnsureValue(value); + if (v == null || (!ValidateInteger(v, false) && !ValidateString(v, false))) + throw new ArgumentException("Can not convert {0} to Char.".FormatWith(CultureInfo.InvariantCulture, GetType(value))); + + return Convert.ToChar(v.Value, CultureInfo.InvariantCulture); + } + + /// + /// Performs an explicit conversion from to . + /// + /// The value. + /// The result of the conversion. + public static explicit operator byte(JToken value) + { + JValue v = EnsureValue(value); + if (v == null || !ValidateInteger(v, false)) + throw new ArgumentException("Can not convert {0} to Byte.".FormatWith(CultureInfo.InvariantCulture, GetType(value))); + + return Convert.ToByte(v.Value, CultureInfo.InvariantCulture); + } + /// /// Performs an explicit conversion from to . /// @@ -637,7 +683,24 @@ namespace Newtonsoft.Json.Linq if (v == null || !ValidateInteger(v, true)) throw new ArgumentException("Can not convert {0} to UInt16.".FormatWith(CultureInfo.InvariantCulture, GetType(value))); - return (v.Value != null) ? (ushort?)Convert.ToInt16(v.Value, CultureInfo.InvariantCulture) : null; + return (v.Value != null) ? (ushort?)Convert.ToUInt16(v.Value, CultureInfo.InvariantCulture) : null; + } + + /// + /// Performs an explicit conversion from to . + /// + /// The value. + /// The result of the conversion. + public static explicit operator byte?(JToken value) + { + if (value == null) + return null; + + JValue v = EnsureValue(value); + if (v == null || !ValidateInteger(v, true)) + throw new ArgumentException("Can not convert {0} to Byte.".FormatWith(CultureInfo.InvariantCulture, GetType(value))); + + return (v.Value != null) ? (byte?)Convert.ToByte(v.Value, CultureInfo.InvariantCulture) : null; } /// @@ -777,7 +840,7 @@ namespace Newtonsoft.Json.Linq return null; JValue v = EnsureValue(value); - if (v == null || !ValidateString(v)) + if (v == null || !ValidateString(v, true)) throw new ArgumentException("Can not convert {0} to String.".FormatWith(CultureInfo.InvariantCulture, GetType(value))); return (v.Value != null) ? Convert.ToString(v.Value, CultureInfo.InvariantCulture) : null; @@ -826,6 +889,85 @@ namespace Newtonsoft.Json.Linq return (byte[])v.Value; } + + /// + /// Performs an explicit conversion from to . + /// + /// The value. + /// The result of the conversion. + public static explicit operator Guid(JToken value) + { + JValue v = EnsureValue(value); + if (v == null || !ValidateString(v, false)) + throw new ArgumentException("Can not convert {0} to Guid.".FormatWith(CultureInfo.InvariantCulture, GetType(value))); + + return (v.Value is Guid) ? (Guid)v.Value : new Guid((string)v.Value); + } + + /// + /// Performs an explicit conversion from to . + /// + /// The value. + /// The result of the conversion. + public static explicit operator Guid?(JToken value) + { + JValue v = EnsureValue(value); + if (v == null || !ValidateString(v, true)) + throw new ArgumentException("Can not convert {0} to Guid.".FormatWith(CultureInfo.InvariantCulture, GetType(value))); + + if (v.Value == null) + return null; + + return (v.Value is Guid) ? (Guid)v.Value : new Guid(Convert.ToString(v.Value, CultureInfo.InvariantCulture)); + } + + /// + /// Performs an explicit conversion from to . + /// + /// The value. + /// The result of the conversion. + public static explicit operator TimeSpan(JToken value) + { + JValue v = EnsureValue(value); + if (v == null || !ValidateString(v, false)) + throw new ArgumentException("Can not convert {0} to TimeSpan.".FormatWith(CultureInfo.InvariantCulture, GetType(value))); + + return (v.Value is TimeSpan) ? (TimeSpan)v.Value : ConvertUtils.ParseTimeSpan(Convert.ToString(v.Value, CultureInfo.InvariantCulture)); + } + + /// + /// Performs an explicit conversion from to . + /// + /// The value. + /// The result of the conversion. + public static explicit operator TimeSpan?(JToken value) + { + JValue v = EnsureValue(value); + if (v == null || !ValidateString(v, true)) + throw new ArgumentException("Can not convert {0} to TimeSpan.".FormatWith(CultureInfo.InvariantCulture, GetType(value))); + + if (v.Value == null) + return null; + + return (v.Value is TimeSpan) ? (TimeSpan)v.Value : ConvertUtils.ParseTimeSpan(Convert.ToString(v.Value, CultureInfo.InvariantCulture)); + } + + /// + /// Performs an explicit conversion from to . + /// + /// The value. + /// The result of the conversion. + public static explicit operator Uri(JToken value) + { + JValue v = EnsureValue(value); + if (v == null || !ValidateString(v, true)) + throw new ArgumentException("Can not convert {0} to Uri.".FormatWith(CultureInfo.InvariantCulture, GetType(value))); + + if (v.Value == null) + return null; + + return (v.Value is Uri) ? (Uri)v.Value : new Uri(Convert.ToString(v.Value, CultureInfo.InvariantCulture)); + } #endregion #region Cast to operators @@ -1171,7 +1313,134 @@ namespace Newtonsoft.Json.Linq /// The new object created from the JSON value. public T ToObject() { - return ToObject(new JsonSerializer()); + return (T)ToObject(typeof(T)); + } + + /// + /// Creates the specified .NET type from the . + /// + /// The object type that the token will be deserialized to. + /// The new object created from the JSON value. + public object ToObject(Type objectType) + { + return ToObject(objectType, false); + } + + private object ToObject(Type objectType, bool isNullable) + { + TypeCode typeCode = ConvertUtils.GetTypeCode(objectType); + + switch (typeCode) + { + case TypeCode.Object: + if (!isNullable && ReflectionUtils.IsNullableType(objectType)) + return ToObject(Nullable.GetUnderlyingType(objectType), true); + + // fall back to standard JsonSerializer + break; + case TypeCode.Boolean: + if (isNullable) + return (bool?) this; + + return (bool) this; + case TypeCode.Char: + if (isNullable) + return (char?) this; + + return (char) this; + case TypeCode.SByte: + if (isNullable) + return (sbyte?) this; + + return (sbyte) this; + case TypeCode.Byte: + if (isNullable) + return (byte?) this; + + return (byte) this; + case TypeCode.Int16: + if (isNullable) + return (short?) this; + + return (short) this; + case TypeCode.UInt16: + if (isNullable) + return (ushort?) this; + + return (ushort) this; + case TypeCode.Int32: + if (isNullable) + return (int?) this; + + return (int) this; + case TypeCode.UInt32: + if (isNullable) + return (uint?) this; + + return (uint) this; + case TypeCode.Int64: + if (isNullable) + return (long?) this; + + return (long) this; + case TypeCode.UInt64: + if (isNullable) + return (ulong?) this; + + return (ulong) this; + case TypeCode.Single: + if (isNullable) + return (float?) this; + + return (float) this; + case TypeCode.Double: + if (isNullable) + return (double?) this; + + return (double) this; + case TypeCode.Decimal: + if (isNullable) + return (decimal?) this; + + return (decimal) this; + case TypeCode.DateTime: + if (isNullable) + return (DateTime?) this; + + return (DateTime) this; + case TypeCode.String: + return (string) this; + } + +#if !PocketPC && !NET20 + if (objectType == typeof (DateTimeOffset)) + { + if (isNullable) + return (DateTimeOffset?) this; + + return (DateTimeOffset) this; + } +#endif + if (objectType == typeof (Guid)) + { + if (isNullable) + return (Guid?) this; + + return (Guid) this; + } + if (objectType == typeof (Uri)) + { + return (Uri) this; + } + if (objectType == typeof (TimeSpan)) + { + if (isNullable) + return (TimeSpan?) this; + + return (TimeSpan) this; + } + + return ToObject(objectType, new JsonSerializer()); } /// @@ -1181,12 +1450,23 @@ namespace Newtonsoft.Json.Linq /// The that will be used when creating the object. /// The new object created from the JSON value. public T ToObject(JsonSerializer jsonSerializer) + { + return (T)ToObject(typeof(T), jsonSerializer); + } + + /// + /// Creates the specified .NET type from the using the specified . + /// + /// The object type that the token will be deserialized to. + /// The that will be used when creating the object. + /// The new object created from the JSON value. + public object ToObject(Type objectType, JsonSerializer jsonSerializer) { ValidationUtils.ArgumentNotNull(jsonSerializer, "jsonSerializer"); using (JTokenReader jsonReader = new JTokenReader(this)) { - return jsonSerializer.Deserialize(jsonReader); + return jsonSerializer.Deserialize(jsonReader, objectType); } } diff --git a/Src/Newtonsoft.Json/Linq/JValue.cs b/Src/Newtonsoft.Json/Linq/JValue.cs index 95c41cfc..d9218b05 100644 --- a/Src/Newtonsoft.Json/Linq/JValue.cs +++ b/Src/Newtonsoft.Json/Linq/JValue.cs @@ -66,6 +66,15 @@ namespace Newtonsoft.Json.Linq { } + /// + /// Initializes a new instance of the class with the given value. + /// + /// The value. + public JValue(char value) + : this(value, JTokenType.String) + { + } + /// /// Initializes a new instance of the class with the given value. /// diff --git a/Src/Newtonsoft.Json/Serialization/MemoryTraceWriter.cs b/Src/Newtonsoft.Json/Serialization/MemoryTraceWriter.cs index 7b639db5..0d40ea5a 100644 --- a/Src/Newtonsoft.Json/Serialization/MemoryTraceWriter.cs +++ b/Src/Newtonsoft.Json/Serialization/MemoryTraceWriter.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.Globalization; using System.Text; namespace Newtonsoft.Json.Serialization @@ -40,7 +41,7 @@ namespace Newtonsoft.Json.Serialization /// The trace exception. This parameter is optional. public void Trace(TraceLevel level, string message, Exception ex) { - string traceMessage = DateTime.Now.ToString("yyyy'-'MM'-'dd'T'HH':'mm':'ss'.'fff") + " " + level.ToString("g") + " " + message; + string traceMessage = DateTime.Now.ToString("yyyy'-'MM'-'dd'T'HH':'mm':'ss'.'fff", CultureInfo.InvariantCulture) + " " + level.ToString("g") + " " + message; if (_traceMessages.Count >= 1000) _traceMessages.Dequeue(); diff --git a/Src/Newtonsoft.Json/Utilities/ConvertUtils.cs b/Src/Newtonsoft.Json/Utilities/ConvertUtils.cs index b8de27bc..13c89f9d 100644 --- a/Src/Newtonsoft.Json/Utilities/ConvertUtils.cs +++ b/Src/Newtonsoft.Json/Utilities/ConvertUtils.cs @@ -222,6 +222,15 @@ namespace Newtonsoft.Json.Utilities #endif } + public static TimeSpan ParseTimeSpan(string input) + { +#if !(NET35 || NET20 || PORTABLE || WINDOWS_PHONE) + return TimeSpan.Parse((string) input, CultureInfo.InvariantCulture); +#else + return TimeSpan.Parse((string)input); +#endif + } + internal struct TypeConvertKey : IEquatable { private readonly Type _initialType; @@ -331,12 +340,8 @@ namespace Newtonsoft.Json.Utilities return new Guid((string) initialValue); if (targetType == typeof (Uri)) return new Uri((string) initialValue, UriKind.RelativeOrAbsolute); - if (targetType == typeof (TimeSpan)) -#if !(NET35 || NET20 || PORTABLE || WINDOWS_PHONE) - return TimeSpan.Parse((string) initialValue, CultureInfo.InvariantCulture); -#else - return TimeSpan.Parse((string)initialValue); -#endif + if (targetType == typeof(TimeSpan)) + return ParseTimeSpan((string) initialValue); } #if !(NETFX_CORE || PORTABLE)