-Added JsonReader.ReadAsDecimal

-Fixed losing decimal precision when deserializing decimals
This commit is contained in:
JamesNK
2010-09-12 11:19:48 +00:00
parent 235770442a
commit 207119fefa
7 changed files with 252 additions and 62 deletions
@@ -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"]);
}
}
}
+37 -9
View File
@@ -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
+9 -3
View File
@@ -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>
+60 -16
View File
@@ -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;
+16 -4
View File
@@ -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;
+31 -7
View File
@@ -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);