-Added JsonReader.ReadAsDecimal
-Fixed losing decimal precision when deserializing decimals
This commit is contained in:
@@ -3558,6 +3558,13 @@ keyword such as type of business.""
|
||||
Assert.AreEqual(TimeSpan.FromHours(6), deserializeObject.Offset.Offset);
|
||||
Assert.AreEqual(new DateTime(2000, 1, 1), deserializeObject.Offset.Date);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void DeserializePropertyNullableDateTimeOffsetExact()
|
||||
{
|
||||
NullableDateTimeTestClass d = JsonConvert.DeserializeObject<NullableDateTimeTestClass>("{\"DateTimeOffsetField\":\"\\/Date(946663200000+0600)\\/\"}");
|
||||
Assert.AreEqual(new DateTimeOffset(new DateTime(2000, 1, 1), TimeSpan.FromHours(6)), d.DateTimeOffsetField);
|
||||
}
|
||||
#endif
|
||||
|
||||
public abstract class LogEvent
|
||||
@@ -3582,7 +3589,7 @@ keyword such as type of business.""
|
||||
}
|
||||
|
||||
#if !(NET35 || NET20 || SILVERLIGHT)
|
||||
[Test]
|
||||
[Test]
|
||||
public void SerializeExpandoObject()
|
||||
{
|
||||
dynamic expando = new ExpandoObject();
|
||||
@@ -3620,5 +3627,43 @@ keyword such as type of business.""
|
||||
Assert.AreEqual(expando.Complex.DateTime, (DateTime)o["DateTime"]);
|
||||
}
|
||||
#endif
|
||||
|
||||
[Test]
|
||||
public void DeserializeDecimalExact()
|
||||
{
|
||||
decimal d = JsonConvert.DeserializeObject<decimal>("123456789876543.21");
|
||||
Assert.AreEqual(123456789876543.21m, d);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void DeserializeNullableDecimalExact()
|
||||
{
|
||||
decimal? d = JsonConvert.DeserializeObject<decimal?>("123456789876543.21");
|
||||
Assert.AreEqual(123456789876543.21m, d);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void DeserializeDecimalPropertyExact()
|
||||
{
|
||||
string json = "{Amount:123456789876543.21}";
|
||||
Invoice i = JsonConvert.DeserializeObject<Invoice>(json);
|
||||
Assert.AreEqual(123456789876543.21m, i.Amount);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void DeserializeDecimalArrayExact()
|
||||
{
|
||||
string json = "[123456789876543.21]";
|
||||
IList<decimal> a = JsonConvert.DeserializeObject<IList<decimal>>(json);
|
||||
Assert.AreEqual(123456789876543.21m, a[0]);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void DeserializeDecimalDictionaryExact()
|
||||
{
|
||||
string json = "{'Value':123456789876543.21}";
|
||||
IDictionary<string, decimal> d = JsonConvert.DeserializeObject<IDictionary<string, decimal>>(json);
|
||||
Assert.AreEqual(123456789876543.21m, d["Value"]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -144,26 +144,54 @@ namespace Newtonsoft.Json.Bson
|
||||
public override byte[] ReadAsBytes()
|
||||
{
|
||||
Read();
|
||||
if (TokenType != JsonToken.Bytes)
|
||||
throw new JsonReaderException("Error reading bytes. Expected bytes but got {0}.".FormatWith(CultureInfo.InvariantCulture, TokenType));
|
||||
|
||||
return (byte[])Value;
|
||||
if (TokenType == JsonToken.Null)
|
||||
return null;
|
||||
if (TokenType == JsonToken.Bytes)
|
||||
return (byte[])Value;
|
||||
|
||||
throw new JsonReaderException("Error reading bytes. Expected bytes but got {0}.".FormatWith(CultureInfo.InvariantCulture, TokenType));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads the next JSON token from the stream as a <see cref="Nullable{Decimal}"/>.
|
||||
/// </summary>
|
||||
/// <returns>A <see cref="Nullable{Decimal}"/>.</returns>
|
||||
public override decimal? ReadAsDecimal()
|
||||
{
|
||||
Read();
|
||||
|
||||
if (TokenType == JsonToken.Null)
|
||||
return null;
|
||||
if (TokenType == JsonToken.Integer || TokenType == JsonToken.Float)
|
||||
{
|
||||
SetToken(JsonToken.Float, Convert.ToDecimal(Value));
|
||||
return (decimal)Value;
|
||||
}
|
||||
|
||||
throw new JsonReaderException("Error reading decimal. Expected a number but got {0}.".FormatWith(CultureInfo.InvariantCulture, TokenType));
|
||||
}
|
||||
|
||||
#if !NET20
|
||||
/// <summary>
|
||||
/// Reads the next JSON token from the stream as a <see cref="DateTimeOffset"/>.
|
||||
/// Reads the next JSON token from the stream as a <see cref="Nullable{DateTimeOffset}"/>.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// A <see cref="DateTimeOffset"/>.
|
||||
/// A <see cref="Nullable{DateTimeOffset}"/>.
|
||||
/// </returns>
|
||||
public override DateTimeOffset ReadAsDateTimeOffset()
|
||||
public override DateTimeOffset? ReadAsDateTimeOffset()
|
||||
{
|
||||
Read();
|
||||
if (TokenType != JsonToken.Date)
|
||||
throw new JsonReaderException("Error reading date. Expected bytes but got {0}.".FormatWith(CultureInfo.InvariantCulture, TokenType));
|
||||
|
||||
return new DateTimeOffset((DateTime)Value);
|
||||
if (TokenType == JsonToken.Null)
|
||||
return null;
|
||||
if (TokenType == JsonToken.Date)
|
||||
{
|
||||
SetToken(JsonToken.Date, new DateTimeOffset((DateTime)Value));
|
||||
return (DateTimeOffset)Value;
|
||||
}
|
||||
|
||||
throw new JsonReaderException("Error reading date. Expected bytes but got {0}.".FormatWith(CultureInfo.InvariantCulture, TokenType));
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
@@ -210,12 +210,18 @@ namespace Newtonsoft.Json
|
||||
/// <returns>A <see cref="T:Byte[]"/> or a null reference if the next JSON token is null.</returns>
|
||||
public abstract byte[] ReadAsBytes();
|
||||
|
||||
/// <summary>
|
||||
/// Reads the next JSON token from the stream as a <see cref="Nullable{Decimal}"/>.
|
||||
/// </summary>
|
||||
/// <returns>A <see cref="Nullable{Decimal}"/>.</returns>
|
||||
public abstract decimal? ReadAsDecimal();
|
||||
|
||||
#if !NET20
|
||||
/// <summary>
|
||||
/// Reads the next JSON token from the stream as a <see cref="DateTimeOffset"/>.
|
||||
/// Reads the next JSON token from the stream as a <see cref="Nullable{DateTimeOffset}"/>.
|
||||
/// </summary>
|
||||
/// <returns>A <see cref="DateTimeOffset"/>.</returns>
|
||||
public abstract DateTimeOffset ReadAsDateTimeOffset();
|
||||
/// <returns>A <see cref="Nullable{DateTimeOffset}"/>.</returns>
|
||||
public abstract DateTimeOffset? ReadAsDateTimeOffset();
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -42,6 +42,7 @@ namespace Newtonsoft.Json
|
||||
{
|
||||
Read,
|
||||
ReadAsBytes,
|
||||
ReadAsDecimal,
|
||||
#if !NET20
|
||||
ReadAsDateTimeOffset
|
||||
#endif
|
||||
@@ -332,17 +333,37 @@ namespace Newtonsoft.Json
|
||||
throw CreateJsonReaderException("Unexpected token when reading bytes: {0}. Line {1}, position {2}.", TokenType, _currentLineNumber, _currentLinePosition);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads the next JSON token from the stream as a <see cref="Nullable{Decimal}"/>.
|
||||
/// </summary>
|
||||
/// <returns>A <see cref="Nullable{Decimal}"/>.</returns>
|
||||
public override decimal? ReadAsDecimal()
|
||||
{
|
||||
_readType = ReadType.ReadAsDecimal;
|
||||
if (!ReadInternal())
|
||||
throw CreateJsonReaderException("Unexpected end when reading decimal: Line {0}, position {1}.", _currentLineNumber, _currentLinePosition);
|
||||
|
||||
if (TokenType == JsonToken.Null)
|
||||
return null;
|
||||
if (TokenType == JsonToken.Float)
|
||||
return (decimal?)Value;
|
||||
|
||||
throw CreateJsonReaderException("Unexpected token when reading decimal: {0}. Line {1}, position {2}.", TokenType, _currentLineNumber, _currentLinePosition);
|
||||
}
|
||||
|
||||
#if !NET20
|
||||
/// <summary>
|
||||
/// Reads the next JSON token from the stream as a <see cref="DateTimeOffset"/>.
|
||||
/// Reads the next JSON token from the stream as a <see cref="Nullable{DateTimeOffset}"/>.
|
||||
/// </summary>
|
||||
/// <returns>A <see cref="DateTimeOffset"/>.</returns>
|
||||
public override DateTimeOffset ReadAsDateTimeOffset()
|
||||
public override DateTimeOffset? ReadAsDateTimeOffset()
|
||||
{
|
||||
_readType = ReadType.ReadAsDateTimeOffset;
|
||||
if (!ReadInternal())
|
||||
throw CreateJsonReaderException("Unexpected end when reading date: Line {0}, position {1}.", _currentLineNumber, _currentLinePosition);
|
||||
|
||||
if (TokenType == JsonToken.Null)
|
||||
return null;
|
||||
if (TokenType == JsonToken.Date)
|
||||
return (DateTimeOffset)Value;
|
||||
|
||||
@@ -708,30 +729,53 @@ namespace Newtonsoft.Json
|
||||
object numberValue;
|
||||
JsonToken numberType;
|
||||
|
||||
if (firstChar == '0' && !number.StartsWith("0.", StringComparison.OrdinalIgnoreCase))
|
||||
bool nonBase10 = (firstChar == '0' && !number.StartsWith("0.", StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
if (_readType == ReadType.ReadAsDecimal)
|
||||
{
|
||||
numberValue = number.StartsWith("0x", StringComparison.OrdinalIgnoreCase)
|
||||
? Convert.ToInt64(number, 16)
|
||||
: Convert.ToInt64(number, 8);
|
||||
numberType = JsonToken.Integer;
|
||||
}
|
||||
else if (number.IndexOf(".", StringComparison.OrdinalIgnoreCase) != -1 || number.IndexOf("e", StringComparison.OrdinalIgnoreCase) != -1)
|
||||
{
|
||||
numberValue = Convert.ToDouble(number, CultureInfo.InvariantCulture);
|
||||
if (nonBase10)
|
||||
{
|
||||
// decimal.Parse doesn't support parsing hexadecimal values
|
||||
long integer = number.StartsWith("0x", StringComparison.OrdinalIgnoreCase)
|
||||
? Convert.ToInt64(number, 16)
|
||||
: Convert.ToInt64(number, 8);
|
||||
|
||||
numberValue = Convert.ToDecimal(integer);
|
||||
}
|
||||
else
|
||||
{
|
||||
numberValue = decimal.Parse(number, NumberStyles.Number | NumberStyles.AllowExponent);
|
||||
}
|
||||
|
||||
numberType = JsonToken.Float;
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
if (nonBase10)
|
||||
{
|
||||
numberValue = Convert.ToInt64(number, CultureInfo.InvariantCulture);
|
||||
numberValue = number.StartsWith("0x", StringComparison.OrdinalIgnoreCase)
|
||||
? Convert.ToInt64(number, 16)
|
||||
: Convert.ToInt64(number, 8);
|
||||
numberType = JsonToken.Integer;
|
||||
}
|
||||
catch (OverflowException ex)
|
||||
else if (number.IndexOf(".", StringComparison.OrdinalIgnoreCase) != -1 || number.IndexOf("e", StringComparison.OrdinalIgnoreCase) != -1)
|
||||
{
|
||||
throw new JsonReaderException("JSON integer {0} is too large or small for an Int64.".FormatWith(CultureInfo.InvariantCulture, number), ex);
|
||||
numberValue = Convert.ToDouble(number, CultureInfo.InvariantCulture);
|
||||
numberType = JsonToken.Float;
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
numberValue = Convert.ToInt64(number, CultureInfo.InvariantCulture);
|
||||
}
|
||||
catch (OverflowException ex)
|
||||
{
|
||||
throw new JsonReaderException("JSON integer {0} is too large or small for an Int64.".FormatWith(CultureInfo.InvariantCulture, number), ex);
|
||||
}
|
||||
|
||||
numberType = JsonToken.Integer;
|
||||
numberType = JsonToken.Integer;
|
||||
}
|
||||
}
|
||||
|
||||
_buffer.Position = 0;
|
||||
|
||||
@@ -318,14 +318,26 @@ namespace Newtonsoft.Json
|
||||
return data;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads the next JSON token from the stream as a <see cref="Nullable{Decimal}"/>.
|
||||
/// </summary>
|
||||
/// <returns>A <see cref="Nullable{Decimal}"/>.</returns>
|
||||
public override decimal? ReadAsDecimal()
|
||||
{
|
||||
decimal? d = _reader.ReadAsDecimal();
|
||||
|
||||
ValidateCurrentToken();
|
||||
return d;
|
||||
}
|
||||
|
||||
#if !NET20
|
||||
/// <summary>
|
||||
/// Reads the next JSON token from the stream as a <see cref="DateTimeOffset"/>.
|
||||
/// Reads the next JSON token from the stream as a <see cref="Nullable{DateTimeOffset}"/>.
|
||||
/// </summary>
|
||||
/// <returns>A <see cref="DateTimeOffset"/>.</returns>
|
||||
public override DateTimeOffset ReadAsDateTimeOffset()
|
||||
/// <returns>A <see cref="Nullable{DateTimeOffset}"/>.</returns>
|
||||
public override DateTimeOffset? ReadAsDateTimeOffset()
|
||||
{
|
||||
DateTimeOffset dateTimeOffset = _reader.ReadAsDateTimeOffset();
|
||||
DateTimeOffset? dateTimeOffset = _reader.ReadAsDateTimeOffset();
|
||||
|
||||
ValidateCurrentToken();
|
||||
return dateTimeOffset;
|
||||
|
||||
@@ -46,25 +46,49 @@ namespace Newtonsoft.Json.Linq
|
||||
SetToken(JsonToken.Bytes, data);
|
||||
}
|
||||
|
||||
if (TokenType == JsonToken.Bytes)
|
||||
return (byte[])Value;
|
||||
if (TokenType == JsonToken.Null)
|
||||
return null;
|
||||
if (TokenType == JsonToken.Bytes)
|
||||
return (byte[])Value;
|
||||
|
||||
throw new JsonReaderException("Error reading bytes. Expected bytes but got {0}.".FormatWith(CultureInfo.InvariantCulture, TokenType));
|
||||
}
|
||||
|
||||
#if !NET20
|
||||
/// <summary>
|
||||
/// Reads the next JSON token from the stream as a <see cref="DateTimeOffset"/>.
|
||||
/// Reads the next JSON token from the stream as a <see cref="Nullable{Decimal}"/>.
|
||||
/// </summary>
|
||||
/// <returns>A <see cref="DateTimeOffset"/>.</returns>
|
||||
public override DateTimeOffset ReadAsDateTimeOffset()
|
||||
/// <returns>A <see cref="Nullable{Decimal}"/>.</returns>
|
||||
public override decimal? ReadAsDecimal()
|
||||
{
|
||||
Read();
|
||||
|
||||
if (TokenType == JsonToken.Null)
|
||||
return null;
|
||||
if (TokenType == JsonToken.Integer || TokenType == JsonToken.Float)
|
||||
{
|
||||
SetToken(JsonToken.Float, Convert.ToDecimal(Value));
|
||||
return (decimal) Value;
|
||||
}
|
||||
|
||||
throw new JsonReaderException("Error reading decimal. Expected a number but got {0}.".FormatWith(CultureInfo.InvariantCulture, TokenType));
|
||||
}
|
||||
|
||||
#if !NET20
|
||||
/// <summary>
|
||||
/// Reads the next JSON token from the stream as a <see cref="Nullable{DateTimeOffset}"/>.
|
||||
/// </summary>
|
||||
/// <returns>A <see cref="Nullable{DateTimeOffset}"/>.</returns>
|
||||
public override DateTimeOffset? ReadAsDateTimeOffset()
|
||||
{
|
||||
Read();
|
||||
|
||||
if (TokenType == JsonToken.Null)
|
||||
return null;
|
||||
if (TokenType == JsonToken.Date)
|
||||
return new DateTimeOffset((DateTime)Value);
|
||||
{
|
||||
SetToken(JsonToken.Date, new DateTimeOffset((DateTime)Value));
|
||||
return (DateTimeOffset)Value;
|
||||
}
|
||||
|
||||
throw new JsonReaderException("Error reading date. Expected bytes but got {0}.".FormatWith(CultureInfo.InvariantCulture, TokenType));
|
||||
}
|
||||
|
||||
@@ -114,7 +114,7 @@ namespace Newtonsoft.Json.Serialization
|
||||
if (reader == null)
|
||||
throw new ArgumentNullException("reader");
|
||||
|
||||
if (reader.TokenType == JsonToken.None && !reader.Read())
|
||||
if (reader.TokenType == JsonToken.None && !ReadForType(reader, objectType, null))
|
||||
return null;
|
||||
|
||||
return CreateValueNonProperty(reader, objectType, GetContractSafe(objectType));
|
||||
@@ -607,7 +607,8 @@ namespace Newtonsoft.Json.Serialization
|
||||
throw new JsonSerializationException("Could not convert string '{0}' to dictionary key type '{1}'. Create a TypeConverter to convert from the string to the key type object.".FormatWith(CultureInfo.InvariantCulture, reader.Value, contract.DictionaryKeyType), ex);
|
||||
}
|
||||
|
||||
CheckedRead(reader);
|
||||
if (!ReadForType(reader, contract.DictionaryValueType, null))
|
||||
throw new JsonSerializationException("Unexpected end when deserializing object.");
|
||||
|
||||
try
|
||||
{
|
||||
@@ -651,6 +652,23 @@ namespace Newtonsoft.Json.Serialization
|
||||
});
|
||||
}
|
||||
|
||||
private bool ReadForTypeArrayHack(JsonReader reader, Type t)
|
||||
{
|
||||
// this is a nasty hack because calling ReadAsDecimal for example will error when we hit the end of the array
|
||||
// need to think of a better way of doing this
|
||||
try
|
||||
{
|
||||
return ReadForType(reader, t, null);
|
||||
}
|
||||
catch (JsonReaderException)
|
||||
{
|
||||
if (reader.TokenType == JsonToken.EndArray)
|
||||
return true;
|
||||
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
private object PopulateList(IWrappedCollection wrappedList, JsonReader reader, string reference, JsonArrayContract contract)
|
||||
{
|
||||
object list = wrappedList.UnderlyingCollection;
|
||||
@@ -662,7 +680,7 @@ namespace Newtonsoft.Json.Serialization
|
||||
|
||||
int initialDepth = reader.Depth;
|
||||
|
||||
while (reader.Read())
|
||||
while (ReadForTypeArrayHack(reader, contract.CollectionItemType))
|
||||
{
|
||||
switch (reader.TokenType)
|
||||
{
|
||||
@@ -896,6 +914,36 @@ namespace Newtonsoft.Json.Serialization
|
||||
return createdObject;
|
||||
}
|
||||
|
||||
private bool ReadForType(JsonReader reader, Type t, JsonConverter propertyConverter)
|
||||
{
|
||||
// don't read properties with converters as a specific value
|
||||
// the value might be a string which will then get converted which will error if read as date for example
|
||||
bool hasConverter = (GetConverter(GetContractSafe(t), propertyConverter) != null);
|
||||
|
||||
if (hasConverter)
|
||||
return reader.Read();
|
||||
|
||||
if (t == typeof(byte[]))
|
||||
{
|
||||
reader.ReadAsBytes();
|
||||
return true;
|
||||
}
|
||||
else if ((t == typeof(decimal) || t == typeof(decimal?)))
|
||||
{
|
||||
reader.ReadAsDecimal();
|
||||
return true;
|
||||
}
|
||||
#if !NET20
|
||||
else if ((t == typeof(DateTimeOffset) || t == typeof(DateTimeOffset?)))
|
||||
{
|
||||
reader.ReadAsDateTimeOffset();
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
return reader.Read();
|
||||
}
|
||||
|
||||
private object PopulateObject(object newObject, JsonReader reader, JsonObjectContract contract, string id)
|
||||
{
|
||||
contract.InvokeOnDeserializing(newObject, Serializer.Context);
|
||||
@@ -928,25 +976,8 @@ namespace Newtonsoft.Json.Serialization
|
||||
continue;
|
||||
}
|
||||
|
||||
// don't read properties with converters as a specific value
|
||||
// the value might be a string which will then get converted which will error if read as date for example
|
||||
bool hasConverter = (GetConverter(GetContractSafe(property.PropertyType), property.Converter) != null);
|
||||
|
||||
if (property.PropertyType == typeof(byte[]) && !hasConverter)
|
||||
{
|
||||
reader.ReadAsBytes();
|
||||
}
|
||||
#if !NET20
|
||||
else if (property.PropertyType == typeof(DateTimeOffset) && !hasConverter)
|
||||
{
|
||||
reader.ReadAsDateTimeOffset();
|
||||
}
|
||||
#endif
|
||||
else
|
||||
{
|
||||
if (!reader.Read())
|
||||
throw new JsonSerializationException("Unexpected end when setting {0}'s value.".FormatWith(CultureInfo.InvariantCulture, memberName));
|
||||
}
|
||||
if (!ReadForType(reader, property.PropertyType, property.Converter))
|
||||
throw new JsonSerializationException("Unexpected end when setting {0}'s value.".FormatWith(CultureInfo.InvariantCulture, memberName));
|
||||
|
||||
SetRequiredProperty(reader, property, requiredProperties);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user