-Added IgnoreAll to DefaultValueHandling which ignores all default class/struct/privative values

-Added support for DataMemberAttribute's EmitDefaultValue
-Fixed losing decimal precision when writing JValues
-Fixed error when calling ToList on empty JObjects and JArrays
This commit is contained in:
James Newton-King
2012-09-01 13:34:07 +12:00
parent 028c287cff
commit d9c8d78d77
20 changed files with 312 additions and 39 deletions
+11 -6
View File
@@ -31,11 +31,11 @@
<para>Convert JSON to and from XML</para>
</listItem>
<listItem>
<para>Supports .NET 2, .NET 3.5, .NET 4, Silverlight, Windows Phone and Windows 8</para>
<para>Supports .NET 2, .NET 3.5, .NET 4, .NET 4.5, Silverlight, Windows Phone and Windows 8 RT</para>
</listItem>
</list>
<para>The JSON serializer is a good choice when the JSON you are reading or writing maps closely to a .NET class.</para>
<para>The JSON serializer in Json.NET is a good choice when the JSON you are reading or writing maps closely to a .NET class.</para>
<para>LINQ to JSON is good for situations where you are only interested in
getting values from JSON, you don't have a class to serialize or deserialize to,
or the JSON is radically different from your class and you need to manually read
@@ -72,19 +72,24 @@ and write from your objects.</para>
was required and Json.NET will split into the three major classes it still uses
today, JsonReader, JsonWriter and JsonSerializer.</para>
<para>Json.NET was first released in June 2006. Since then Json.NET has been downloaded
hundreds of thousands of times by developers and is used in a number of major open
hundreds of thousands of times by developers around the world. It is used in many major open
source projects including <externalLink>
<linkText>Mono</linkText>
<linkUri>http://www.mono-project.com/</linkUri>
<linkTarget>_blank</linkTarget>
</externalLink>,
an open source implementation
of the .NET framework, <externalLink>
of the .NET framework; <externalLink>
<linkText>RavenDB</linkText>
<linkUri>http://ravendb.net/</linkUri>
<linkTarget>_blank</linkTarget>
</externalLink>,
a JSON based documentat database, and <externalLink>
a JSON based documentat database; <externalLink>
<linkText>SignalR</linkText>
<linkUri>http://signalr.net/</linkUri>
<linkTarget>_blank</linkTarget>
</externalLink>,
an async library for building real-time, multi-user interactive web applications; and <externalLink>
<linkText>ASP.NET Web API</linkText>
<linkUri>http://www.asp.net/web-api</linkUri>
<linkTarget>_blank</linkTarget>
@@ -101,7 +106,7 @@ and write from your objects.</para>
<section>
<title>Donate</title>
<content><para>Json.NET is a free open source project developed in my own time.</para>
<para>I really appreciate your feedback and support for Json.NET and its future development.</para></content>
<para>I really appreciate your feedback and donations help support Json.NET and its future development.</para></content>
</section>
<markup>
+2 -2
View File
@@ -84,10 +84,10 @@
<content>
<para><codeEntityReference>T:Newtonsoft.Json.DefaultValueHandling</codeEntityReference>
is an option on the JsonSerializer and controls how the serializer handles
properties with a default value. Setting a value of DefaultValueHandling.Ignore
properties with a default value. Setting a value of DefaultValueHandling.IgnoreAll
will make the JsonSerializer skip writing any properties that have a default
value to the JSON result. For object references this will be null. For value
types like int and DateTime the serializer will skip the default unitialized
types like int and DateTime the serializer will skip the default uninitialized
value for that value type.</para>
<para>Json.NET also allows you to customize what the default value of an individual
+9 -2
View File
@@ -125,11 +125,18 @@
</tableHeader>
<row>
<entry><para><legacyBold>Include</legacyBold></para></entry>
<entry><para>By default Json.NET will write a field/property value to JSON when serializing if the value is the same as the field/property's default value. The Json.NET deserializer will continue setting a field/property if the JSON value is the same as the default value.</para></entry>
<entry><para>By default Json.NET will write a field/property value to JSON when serializing if the value is the same as the field/property's default value.
The Json.NET deserializer will continue setting a field/property if the JSON value is the same as the default value.</para></entry>
</row>
<row>
<entry><para><legacyBold>Ignore</legacyBold></para></entry>
<entry><para>Json.NET will skip writing a field/property value to JSON if the value is the same as the field/property's default value. The Json.NET deserializer will skip setting a .NET object's field/property if the JSON value is the same as the default value.</para></entry>
<entry><para>Json.NET will skip writing a field/property value to JSON if the value is null or is the same as the value specified in field/property's <codeEntityReference>T:System.ComponentModel.DefaultValueAttribute</codeEntityReference>. The Json.NET deserializer will skip setting a .NET object's field/property if the JSON value is the same as the default value.</para></entry>
</row>
<row>
<entry><para><legacyBold>IgnoreAll</legacyBold></para></entry>
<entry><para>Json.NET will skip writing a field/property value to JSON if the value is the same as the field/property's default value, or the custom
value specified in <codeEntityReference>T:System.ComponentModel.DefaultValueAttribute</codeEntityReference> if it is present. The Json.NET deserializer
will skip setting a .NET object's field/property if the JSON value is the same as the default value.</para></entry>
</row>
</table>
<para>DefaultValueHandling can also be customized on individual properties with JsonPropertyAttribute.</para>
-1
View File
@@ -141,7 +141,6 @@
<None Include="ContractResolver.aml" />
<None Include="ConvertingJSONandXML.aml" />
<None Include="CreatingLINQtoJSON.aml" />
<None Include="Copy of ContractResolver.aml" />
<None Include="JsonSchema.aml" />
<None Include="JsonSchema.aml" />
<None Include="ParsingLINQtoJSON.aml" />
@@ -725,7 +725,7 @@ namespace Newtonsoft.Json.Tests.Documentation
string ignored = JsonConvert.SerializeObject(invoice,
Formatting.Indented,
new JsonSerializerSettings { DefaultValueHandling = DefaultValueHandling.Ignore });
new JsonSerializerSettings { DefaultValueHandling = DefaultValueHandling.IgnoreAll });
// {
// "Company": "Acme Ltd.",
@@ -701,7 +701,7 @@ namespace Newtonsoft.Json.Tests.Linq
// "Enabled": true,
// "Price": 2.45,
// "StockCount": 9000,
// "StockValue": 22050.0
// "StockValue": 22050.00
// }
Assert.AreEqual(@"{
@@ -709,7 +709,7 @@ namespace Newtonsoft.Json.Tests.Linq
""Enabled"": true,
""Price"": 2.45,
""StockCount"": 9000,
""StockValue"": 22050.0
""StockValue"": 22050.00
}", json);
}
}
@@ -561,5 +561,21 @@ Parameter name: index",
JArray.Parse(json);
});
}
[Test]
public void ToListOnEmptyArray()
{
string json = @"{""decks"":[]}";
JArray decks = (JArray)JObject.Parse(json)["decks"];
IList<JToken> l = decks.ToList();
Assert.AreEqual(0, l.Count);
json = @"{""decks"":[1]}";
decks = (JArray)JObject.Parse(json)["decks"];
l = decks.ToList();
Assert.AreEqual(1, l.Count);
}
}
}
@@ -1846,5 +1846,24 @@ Parameter name: arrayIndex",
Assert.IsFalse(o1.DeepEquals(null));
}
[Test]
public void ToListOnEmptyObject()
{
JObject o = JObject.Parse(@"{}");
IList<JToken> l1 = o.ToList<JToken>();
Assert.AreEqual(0, l1.Count);
IList<KeyValuePair<string, JToken>> l2 = o.ToList<KeyValuePair<string, JToken>>();
Assert.AreEqual(0, l2.Count);
o = JObject.Parse(@"{'hi':null}");
l1 = o.ToList<JToken>();
Assert.AreEqual(1, l1.Count);
l2 = o.ToList<KeyValuePair<string, JToken>>();
Assert.AreEqual(1, l2.Count);
}
}
}
@@ -314,5 +314,33 @@ namespace Newtonsoft.Json.Tests.Linq
Assert.AreEqual("5.2", json);
}
public class Rate
{
public decimal Compoundings { get; set; }
}
private readonly Rate rate = new Rate { Compoundings = 12.166666666666666666666666667m };
[Test]
public void WriteFullDecimalPrecision()
{
var jTokenWriter = new JTokenWriter();
new JsonSerializer().Serialize(jTokenWriter, rate);
string json = jTokenWriter.Token.ToString();
Assert.AreEqual(@"{
""Compoundings"": 12.166666666666666666666666667
}", json);
}
[Test]
public void RoundTripDecimal()
{
var jTokenWriter = new JTokenWriter();
new JsonSerializer().Serialize(jTokenWriter, rate);
var rate2 = new JsonSerializer().Deserialize<Rate>(new JTokenReader(jTokenWriter.Token));
Assert.AreEqual(rate.Compoundings, rate2.Compoundings);
}
}
}
@@ -76,5 +76,5 @@ using System.Security;
// by using the '*' as shown below:
[assembly: AssemblyVersion("4.5.0.0")]
#if !PocketPC
[assembly: AssemblyFileVersion("4.5.8.15215")]
[assembly: AssemblyFileVersion("4.5.8.15301")]
#endif
@@ -25,6 +25,12 @@
using System;
using System.ComponentModel;
using System.IO;
using System.Runtime.Serialization;
#if !(SILVERLIGHT || PocketPC || NET20 || NET35 || NETFX_CORE || PORTABLE)
using System.Runtime.Serialization.Json;
#endif
using System.Text;
using Newtonsoft.Json.Tests.TestObjects;
#if !NETFX_CORE
using NUnit.Framework;
@@ -57,26 +63,33 @@ namespace Newtonsoft.Json.Tests.Serialization
Formatting.Indented,
new JsonSerializerSettings { });
// {
// "Company": "Acme Ltd.",
// "Amount": 50.0,
// "Paid": false,
// "PaidDate": null,
// "FollowUpDays": 30,
// "FollowUpEmailAddress": ""
// }
Assert.AreEqual(@"{
""Company"": ""Acme Ltd."",
""Amount"": 50.0,
""Paid"": false,
""PaidDate"": null,
""FollowUpDays"": 30,
""FollowUpEmailAddress"": """"
}", included);
string ignored = JsonConvert.SerializeObject(invoice,
Formatting.Indented,
new JsonSerializerSettings { DefaultValueHandling = DefaultValueHandling.Ignore });
// {
// "Company": "Acme Ltd.",
// "Amount": 50.0
// }
Assert.AreEqual(@"{
""Company"": ""Acme Ltd."",
""Amount"": 50.0,
""Paid"": false
}", ignored);
Console.WriteLine(included);
Console.WriteLine(ignored);
string ignoredAll = JsonConvert.SerializeObject(invoice,
Formatting.Indented,
new JsonSerializerSettings { DefaultValueHandling = DefaultValueHandling.IgnoreAll });
Assert.AreEqual(@"{
""Company"": ""Acme Ltd."",
""Amount"": 50.0
}", ignoredAll);
}
[Test]
@@ -172,5 +185,126 @@ namespace Newtonsoft.Json.Tests.Serialization
Assert.IsFalse(MathUtils.ApproxEquals(1000.0, 1000.000000000001));
Assert.IsFalse(MathUtils.ApproxEquals(0.0, 0.00001));
}
#if !NET20
[Test]
public void EmitDefaultValueTest()
{
EmitDefaultValueClass c = new EmitDefaultValueClass();
#if !(SILVERLIGHT || PocketPC || NET20 || NET35 || NETFX_CORE || PORTABLE)
DataContractJsonSerializer jsonSerializer = new DataContractJsonSerializer(typeof(EmitDefaultValueClass));
MemoryStream ms = new MemoryStream();
jsonSerializer.WriteObject(ms, c);
Assert.AreEqual("{}", Encoding.UTF8.GetString(ms.ToArray()));
#endif
string json = JsonConvert.SerializeObject(c);
Assert.AreEqual("{}", json);
}
#endif
[Test]
public void DefaultValueHandlingPropertyTest()
{
DefaultValueHandlingPropertyClass c = new DefaultValueHandlingPropertyClass();
string json = JsonConvert.SerializeObject(c, Formatting.Indented);
Assert.AreEqual(@"{
""IntInclude"": 0,
""IntDefault"": 0
}", json);
json = JsonConvert.SerializeObject(c, Formatting.Indented, new JsonSerializerSettings
{
DefaultValueHandling = DefaultValueHandling.IgnoreAll
});
Assert.AreEqual(@"{
""IntInclude"": 0
}", json);
json = JsonConvert.SerializeObject(c, Formatting.Indented, new JsonSerializerSettings
{
DefaultValueHandling = DefaultValueHandling.Include
});
Assert.AreEqual(@"{
""IntInclude"": 0,
""IntDefault"": 0
}", json);
}
}
public struct DefaultStruct
{
public string Default { get; set; }
}
public class DefaultValueHandlingPropertyClass
{
[JsonProperty(DefaultValueHandling = DefaultValueHandling.IgnoreAll)]
public int IntIgnore { get; set; }
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Include)]
public int IntInclude { get; set; }
[JsonProperty]
public int IntDefault { get; set; }
}
#if !NET20
[DataContract]
public class EmitDefaultValueClass
{
[DataMember(EmitDefaultValue = false)]
public Guid Guid { get; set; }
[DataMember(EmitDefaultValue = false)]
public TimeSpan TimeSpan { get; set; }
[DataMember(EmitDefaultValue = false)]
public DateTime DateTime { get; set; }
[DataMember(EmitDefaultValue = false)]
public DateTimeOffset DateTimeOffset { get; set; }
[DataMember(EmitDefaultValue = false)]
public decimal Decimal { get; set; }
[DataMember(EmitDefaultValue = false)]
public int Integer { get; set; }
[DataMember(EmitDefaultValue = false)]
public double Double { get; set; }
[DataMember(EmitDefaultValue = false)]
public bool Boolean { get; set; }
[DataMember(EmitDefaultValue = false)]
public DefaultStruct Struct { get; set; }
[DataMember(EmitDefaultValue = false)]
public StringComparison Enum { get; set; }
[DataMember(EmitDefaultValue = false)]
public Guid? NullableGuid { get; set; }
[DataMember(EmitDefaultValue = false)]
public TimeSpan? NullableTimeSpan { get; set; }
[DataMember(EmitDefaultValue = false)]
public DateTime? NullableDateTime { get; set; }
[DataMember(EmitDefaultValue = false)]
public DateTimeOffset? NullableDateTimeOffset { get; set; }
[DataMember(EmitDefaultValue = false)]
public decimal? NullableDecimal { get; set; }
[DataMember(EmitDefaultValue = false)]
public int? NullableInteger { get; set; }
[DataMember(EmitDefaultValue = false)]
public double? NullableDouble { get; set; }
[DataMember(EmitDefaultValue = false)]
public bool? NullableBoolean { get; set; }
[DataMember(EmitDefaultValue = false)]
public DefaultStruct? NullableStruct { get; set; }
[DataMember(EmitDefaultValue = false)]
public StringComparison? NullableEnum { get; set; }
[DataMember(EmitDefaultValue = false)]
public object Object { get; set; }
}
#endif
}
+13 -2
View File
@@ -24,6 +24,7 @@
#endregion
using System;
using System.ComponentModel;
namespace Newtonsoft.Json
{
@@ -44,7 +45,9 @@ namespace Newtonsoft.Json
Include = 0,
/// <summary>
/// Ignore members where the member value is the same as the member's default value when serializing objects
/// so that is is not written to JSON, and ignores setting members when the JSON value equals the member's default value.
/// so that is is not written to JSON. This option will only ignore <c>null</c> values for object and nullable types
/// unless the property explicitly has a <see cref="DefaultValueAttribute"/> specified. To ignore default values
/// for all types use the <c>IgnoreAll</c> option.
/// </summary>
Ignore = 1,
/// <summary>
@@ -55,6 +58,14 @@ namespace Newtonsoft.Json
/// Ignore members where the member value is the same as the member's default value when serializing objects
/// and sets members to their default value when deserializing.
/// </summary>
IgnoreAndPopulate = Ignore | Populate
IgnoreAndPopulate = Ignore | Populate,
/// <summary>
/// Ignore members where the member value is the same as the member's default value when serializing objects
/// so that is is not written to JSON, and ignores setting members when the JSON value equals the member's default value.
/// This option will ignore all default values (e.g. <c>null</c> for objects and nullable typesl; <c>0</c> for integers,
/// decimals and floating point numbers; and <c>false</c> for booleans). The default value ignored can be override by
/// the <see cref="DefaultValueAttribute"/>.
/// </summary>
IgnoreAll = 4
}
}
+1 -1
View File
@@ -209,7 +209,7 @@ namespace Newtonsoft.Json
get { return _defaultValueHandling; }
set
{
if (value < DefaultValueHandling.Include || value > DefaultValueHandling.IgnoreAndPopulate)
if (value < DefaultValueHandling.Include || value > DefaultValueHandling.IgnoreAll)
throw new ArgumentOutOfRangeException("value");
_defaultValueHandling = value;
+1 -1
View File
@@ -503,7 +503,7 @@ namespace Newtonsoft.Json.Linq
throw new ArgumentNullException("array");
if (arrayIndex < 0)
throw new ArgumentOutOfRangeException("arrayIndex", "arrayIndex is less than 0.");
if (arrayIndex >= array.Length)
if (arrayIndex >= array.Length && arrayIndex != 0)
throw new ArgumentException("arrayIndex is equal to or greater than the length of array.");
if (Count > array.Length - arrayIndex)
throw new ArgumentException("The number of elements in the source JObject is greater than the available space from arrayIndex to the end of the destination array.");
+1 -1
View File
@@ -472,7 +472,7 @@ namespace Newtonsoft.Json.Linq
throw new ArgumentNullException("array");
if (arrayIndex < 0)
throw new ArgumentOutOfRangeException("arrayIndex", "arrayIndex is less than 0.");
if (arrayIndex >= array.Length)
if (arrayIndex >= array.Length && arrayIndex != 0)
throw new ArgumentException("arrayIndex is equal to or greater than the length of array.");
if (Count > array.Length - arrayIndex)
throw new ArgumentException("The number of elements in the source JObject is greater than the available space from arrayIndex to the end of the destination array.");
+6 -2
View File
@@ -539,8 +539,12 @@ namespace Newtonsoft.Json.Linq
writer.WriteValue(Convert.ToInt64(_value, CultureInfo.InvariantCulture));
return;
case JTokenType.Float:
if (_value is float)
writer.WriteValue(_value);
if (_value is decimal)
writer.WriteValue((decimal)_value);
else if (_value is double)
writer.WriteValue((double)_value);
else if (_value is float)
writer.WriteValue((float)_value);
else
writer.WriteValue(Convert.ToDouble(_value, CultureInfo.InvariantCulture));
return;
@@ -90,7 +90,7 @@ using System.Security;
// by using the '*' as shown below:
[assembly: AssemblyVersion("4.5.0.0")]
#if !PocketPC
[assembly: AssemblyFileVersion("4.5.8.15215")]
[assembly: AssemblyFileVersion("4.5.8.15301")]
#endif
[assembly: CLSCompliant(true)]
@@ -968,6 +968,7 @@ namespace Newtonsoft.Json.Serialization
{
property._required = propertyAttribute._required;
property.Order = propertyAttribute._order;
property.DefaultValueHandling = propertyAttribute._defaultValueHandling;
hasMemberAttribute = true;
}
#if !PocketPC && !NET20
@@ -975,6 +976,7 @@ namespace Newtonsoft.Json.Serialization
{
property._required = (dataMemberAttribute.IsRequired) ? Required.AllowNull : Required.Default;
property.Order = (dataMemberAttribute.Order != -1) ? (int?) dataMemberAttribute.Order : null;
property.DefaultValueHandling = (!dataMemberAttribute.EmitDefaultValue) ? (DefaultValueHandling?) DefaultValueHandling.IgnoreAll : null;
hasMemberAttribute = true;
}
#endif
@@ -1012,7 +1014,6 @@ namespace Newtonsoft.Json.Serialization
property.DefaultValue = (defaultValueAttribute != null) ? defaultValueAttribute.Value : null;
property.NullValueHandling = (propertyAttribute != null) ? propertyAttribute._nullValueHandling : null;
property.DefaultValueHandling = (propertyAttribute != null) ? propertyAttribute._defaultValueHandling : null;
property.ReferenceLoopHandling = (propertyAttribute != null) ? propertyAttribute._referenceLoopHandling : null;
property.ObjectCreationHandling = (propertyAttribute != null) ? propertyAttribute._objectCreationHandling : null;
property.TypeNameHandling = (propertyAttribute != null) ? propertyAttribute._typeNameHandling : null;
@@ -204,10 +204,16 @@ namespace Newtonsoft.Json.Serialization
memberValue == null)
return false;
if (HasFlag(property.DefaultValueHandling.GetValueOrDefault(Serializer.DefaultValueHandling), DefaultValueHandling.Ignore)
DefaultValueHandling defaultValueHandling = property.DefaultValueHandling.GetValueOrDefault(Serializer.DefaultValueHandling);
if (HasFlag(defaultValueHandling, DefaultValueHandling.Ignore)
&& MiscellaneousUtils.ValueEquals(memberValue, property.DefaultValue))
return false;
if (defaultValueHandling == DefaultValueHandling.IgnoreAll
&& MiscellaneousUtils.ValueEquals(memberValue, property.DefaultValue ?? ReflectionUtils.GetDefaultValue(property.PropertyType)))
return false;
return true;
}
@@ -1015,5 +1015,48 @@ namespace Newtonsoft.Json.Utilities
return isMethodOverriden;
}
public static object GetDefaultValue(Type type)
{
if (!type.IsValueType)
return null;
switch (Type.GetTypeCode(type))
{
case TypeCode.Boolean:
return false;
case TypeCode.Char:
case TypeCode.SByte:
case TypeCode.Byte:
case TypeCode.Int16:
case TypeCode.UInt16:
case TypeCode.Int32:
case TypeCode.UInt32:
return 0;
case TypeCode.Int64:
case TypeCode.UInt64:
return 0L;
case TypeCode.Single:
return 0f;
case TypeCode.Double:
return 0.0;
case TypeCode.Decimal:
return 0m;
case TypeCode.DateTime:
return new DateTime();
}
if (type == typeof(Guid))
return new Guid();
if (type == typeof(DateTimeOffset))
return new DateTimeOffset();
if (IsNullable(type))
return null;
// possibly use IL initobj for perf here?
return Activator.CreateInstance(type);
}
}
}