Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added support for EmitDefaultValue in the DataMemberAttribute #73

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,26 @@ private string NoDataMember { set { } }
#endregion

#region Getter/Setters
[DataContract]
public class DataContractEmitDefaultValuePublicGetterSetters
{
public DataContractEmitDefaultValuePublicGetterSetters()
{
DataMemberWithoutName = "dmv";
DatMemberWithName = "dmnv";
}

[DataMember]
public string DataMemberWithoutName { get; set; }

[DataMember(Name = "name", EmitDefaultValue = false)]
public string DatMemberWithName { get; set; }

[IgnoreDataMember]
public string IgnoreDataMember { get; set; }

public string NoDataMember { get; set; }
}

[DataContract]
public class DataContractPublicGetterSetters
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
//-----------------------------------------------------------------------
// <copyright file="<file>.cs" company="The Outercurve Foundation">
// Copyright (c) 2011, The Outercurve Foundation.
//
// Licensed under the MIT License (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.opensource.org/licenses/mit-license.php
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// </copyright>
// <author>Nathan Totten (ntotten.com), Jim Zimmerman (jimzimmerman.com) and Prabir Shrestha (prabir.me)</author>
// <website>https://github.com/facebook-csharp-sdk/simple-json</website>
//-----------------------------------------------------------------------

namespace SimpleJsonTests.DataContractTests
{
#if NUNIT
using TestClass = NUnit.Framework.TestFixtureAttribute;
using TestMethod = NUnit.Framework.TestAttribute;
using TestCleanup = NUnit.Framework.TearDownAttribute;
using TestInitialize = NUnit.Framework.SetUpAttribute;
using ClassCleanup = NUnit.Framework.TestFixtureTearDownAttribute;
using ClassInitialize = NUnit.Framework.TestFixtureSetUpAttribute;
using NUnit.Framework;
#else
#if NETFX_CORE
using Microsoft.VisualStudio.TestPlatform.UnitTestFramework;
#else
using Microsoft.VisualStudio.TestTools.UnitTesting;
#endif
#endif

using SimpleJson;

[TestClass]
public class EmitDefaultValueSerializeTests
{
private DataContractEmitDefaultValuePublicGetterSetters _dataContractEmitDefaultValuePublicGetterSetters;

public EmitDefaultValueSerializeTests()
{
_dataContractEmitDefaultValuePublicGetterSetters = new DataContractEmitDefaultValuePublicGetterSetters();
}

[TestMethod]
public void SerializesCorrectlyWithDefaultValue()
{
_dataContractEmitDefaultValuePublicGetterSetters.DatMemberWithName = null;
var result = SimpleJson.SerializeObject(_dataContractEmitDefaultValuePublicGetterSetters, SimpleJson.DataContractJsonSerializerStrategy);

// As the property has a DataMemberAttribute and EmitDefaultValue = false, and the value is false
// there should be a name property in there. (The case is important here)
Assert.IsFalse(result.Contains("name"));
Assert.IsTrue(result.Contains("DataMemberWithoutName"));
}

[TestMethod]
public void SerializesCorrectlyWithoutDefaultValue()
{
_dataContractEmitDefaultValuePublicGetterSetters.DatMemberWithName = "SimpleJson";
var result = SimpleJson.SerializeObject(_dataContractEmitDefaultValuePublicGetterSetters, SimpleJson.DataContractJsonSerializerStrategy);

// As the property has a DataMemberAttribute and EmitDefaultValue = false, and the value is false
// there should be a name property in there. (The case is important here)
Assert.IsTrue(result.Contains("SimpleJson"));
Assert.IsTrue(result.Contains("DataMemberWithoutName"));
}
}
}
1 change: 1 addition & 0 deletions src/SimpleJson.Tests/SimpleJson.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="DataContractTests\EmitDefaultValueSerializeTests.cs" />
<Compile Include="DeserializeGenericListTests.cs" />
<Compile Include="DataContractTests\DataContractSampleClassess.cs" />
<Compile Include="DataContractTests\PrivateFieldsSerializeTests.cs" />
Expand Down
115 changes: 104 additions & 11 deletions src/SimpleJson/SimpleJson.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1244,9 +1244,12 @@ interface IJsonSerializerStrategy
#endif
class PocoJsonSerializerStrategy : IJsonSerializerStrategy
{
public delegate bool EmitPredicate(object value);

internal IDictionary<Type, ReflectionUtils.ConstructorDelegate> ConstructorCache;
internal IDictionary<Type, IDictionary<string, ReflectionUtils.GetDelegate>> GetCache;
internal IDictionary<Type, IDictionary<string, KeyValuePair<Type, ReflectionUtils.SetDelegate>>> SetCache;
protected internal IDictionary<Type, IDictionary<string, EmitPredicate>> EmitPredicateCache;

internal static readonly Type[] EmptyTypes = new Type[0];
internal static readonly Type[] ArrayConstructorParameterTypes = new Type[] { typeof(int) };
Expand All @@ -1270,6 +1273,11 @@ protected virtual string MapClrMemberNameToJsonFieldName(string clrPropertyName)
return clrPropertyName;
}

internal virtual IDictionary<string, EmitPredicate> EmitPredicateFactory(Type type)
{
return null;
}

internal virtual ReflectionUtils.ConstructorDelegate ContructorDelegateFactory(Type key)
{
return ReflectionUtils.GetContructor(key, key.IsArray ? ArrayConstructorParameterTypes : EmptyTypes);
Expand Down Expand Up @@ -1504,10 +1512,21 @@ protected virtual bool TrySerializeUnknownTypes(object input, out object output)
return false;
IDictionary<string, object> obj = new JsonObject();
IDictionary<string, ReflectionUtils.GetDelegate> getters = GetCache[type];
IDictionary<string, EmitPredicate> emitPredicate = EmitPredicateCache == null ? null : EmitPredicateCache[type];
foreach (KeyValuePair<string, ReflectionUtils.GetDelegate> getter in getters)
{
if (getter.Value != null)
obj.Add(MapClrMemberNameToJsonFieldName(getter.Key), getter.Value(input));
{
object value = getter.Value(input);
if (emitPredicate != null && emitPredicate.ContainsKey(getter.Key) == true)
{
if (!emitPredicate[getter.Key](value))
{
continue;
}
}
obj.Add(MapClrMemberNameToJsonFieldName(getter.Key), value);
}
}
output = obj;
return true;
Expand All @@ -1527,28 +1546,95 @@ public DataContractJsonSerializerStrategy()
{
GetCache = new ReflectionUtils.ThreadSafeDictionary<Type, IDictionary<string, ReflectionUtils.GetDelegate>>(GetterValueFactory);
SetCache = new ReflectionUtils.ThreadSafeDictionary<Type, IDictionary<string, KeyValuePair<Type, ReflectionUtils.SetDelegate>>>(SetterValueFactory);
EmitPredicateCache = new ReflectionUtils.ThreadSafeDictionary<Type, IDictionary<string, EmitPredicate>>(EmitPredicateFactory);
}

/// <summary>
/// Helper method to supply the name of the json key, either from the DataMemberAttribute or from the MemberInfo
/// </summary>
/// <param name="dataMemberAttribute">DataMemberAttribute</param>
/// <param name="memberInfo"></param>
/// <returns>string with the name in the Json</returns>
private string JsonKey(DataMemberAttribute dataMemberAttribute, MemberInfo memberInfo)
{
return string.IsNullOrEmpty(dataMemberAttribute.Name) ? memberInfo.Name : dataMemberAttribute.Name;
}

/// <summary>
/// Create a default value for a type, this usually is "null" for reference type, but for other, e.g. bool it's false or for int it's 0
/// </summary>
/// <param name="type">Type to create a default for</param>
/// <returns>Default for type</returns>
private static object Default(Type type)
{
#if SIMPLE_JSON_TYPEINFO
if (type.GetTypeInfo().IsValueType)
#else
if (type.IsValueType)
#endif
{
return Activator.CreateInstance(type);
}

return null;
}

/// <summary>
/// Generate a cache with predicates which decides if the value needs to be emitted
/// Would have been nicer to integrate it into the getter, but this would mean more changes
/// </summary>
/// <param name="type"></param>
internal override IDictionary<string, EmitPredicate> EmitPredicateFactory(Type type)
{
IDictionary<string, EmitPredicate> result = new Dictionary<string, EmitPredicate>();
DataContractAttribute dataContractAttribute = (DataContractAttribute)ReflectionUtils.GetAttribute(type, typeof(DataContractAttribute));
if (dataContractAttribute == null) {
return result;
}
foreach (PropertyInfo propertyInfo in ReflectionUtils.GetProperties(type))
{
DataMemberAttribute dataMemberAttribute;
if (CanAdd(propertyInfo, out dataMemberAttribute))
{
string jsonKey = JsonKey(dataMemberAttribute, propertyInfo);
if (dataMemberAttribute != null && dataMemberAttribute.EmitDefaultValue == false)
{
object def = Default(propertyInfo.PropertyType);
result[jsonKey] = delegate(object value) { return !Equals(def, value); };
}
}
}
return result;
}

internal override IDictionary<string, ReflectionUtils.GetDelegate> GetterValueFactory(Type type)
{
bool hasDataContract = ReflectionUtils.GetAttribute(type, typeof(DataContractAttribute)) != null;
if (!hasDataContract)
DataContractAttribute dataContractAttribute = (DataContractAttribute)ReflectionUtils.GetAttribute(type, typeof(DataContractAttribute));
if (dataContractAttribute == null)
return base.GetterValueFactory(type);

string jsonKey;
DataMemberAttribute dataMemberAttribute;
IDictionary<string, ReflectionUtils.GetDelegate> result = new Dictionary<string, ReflectionUtils.GetDelegate>();
foreach (PropertyInfo propertyInfo in ReflectionUtils.GetProperties(type))
{
if (propertyInfo.CanRead)
{
MethodInfo getMethod = ReflectionUtils.GetGetterMethodInfo(propertyInfo);
if (!getMethod.IsStatic && CanAdd(propertyInfo, out jsonKey))
if (!getMethod.IsStatic && CanAdd(propertyInfo, out dataMemberAttribute))
{
jsonKey = string.IsNullOrEmpty(dataMemberAttribute.Name) ? propertyInfo.Name : dataMemberAttribute.Name;
result[jsonKey] = ReflectionUtils.GetGetMethod(propertyInfo);
}
}
}
foreach (FieldInfo fieldInfo in ReflectionUtils.GetFields(type))
{
if (!fieldInfo.IsStatic && CanAdd(fieldInfo, out jsonKey))
if (!fieldInfo.IsStatic && CanAdd(fieldInfo, out dataMemberAttribute))
{
jsonKey = string.IsNullOrEmpty(dataMemberAttribute.Name) ? fieldInfo.Name : dataMemberAttribute.Name;
result[jsonKey] = ReflectionUtils.GetGetMethod(fieldInfo);
}
}
return result;
}
Expand All @@ -1559,34 +1645,41 @@ public DataContractJsonSerializerStrategy()
if (!hasDataContract)
return base.SetterValueFactory(type);
string jsonKey;
DataMemberAttribute dataMemberAttribute;
IDictionary<string, KeyValuePair<Type, ReflectionUtils.SetDelegate>> result = new Dictionary<string, KeyValuePair<Type, ReflectionUtils.SetDelegate>>();
foreach (PropertyInfo propertyInfo in ReflectionUtils.GetProperties(type))
{
if (propertyInfo.CanWrite)
{
MethodInfo setMethod = ReflectionUtils.GetSetterMethodInfo(propertyInfo);
if (!setMethod.IsStatic && CanAdd(propertyInfo, out jsonKey))
if (!setMethod.IsStatic && CanAdd(propertyInfo, out dataMemberAttribute))
{
jsonKey = string.IsNullOrEmpty(dataMemberAttribute.Name) ? propertyInfo.Name : dataMemberAttribute.Name;
result[jsonKey] = new KeyValuePair<Type, ReflectionUtils.SetDelegate>(propertyInfo.PropertyType, ReflectionUtils.GetSetMethod(propertyInfo));
}
}
}
foreach (FieldInfo fieldInfo in ReflectionUtils.GetFields(type))
{
if (!fieldInfo.IsInitOnly && !fieldInfo.IsStatic && CanAdd(fieldInfo, out jsonKey))
if (!fieldInfo.IsInitOnly && !fieldInfo.IsStatic && CanAdd(fieldInfo, out dataMemberAttribute))
{
jsonKey = string.IsNullOrEmpty(dataMemberAttribute.Name) ? fieldInfo.Name : dataMemberAttribute.Name;
result[jsonKey] = new KeyValuePair<Type, ReflectionUtils.SetDelegate>(fieldInfo.FieldType, ReflectionUtils.GetSetMethod(fieldInfo));
}
}
// todo implement sorting for DATACONTRACT.
return result;
}

private static bool CanAdd(MemberInfo info, out string jsonKey)
private static bool CanAdd(MemberInfo info, out DataMemberAttribute dataMemberAttribute)
{
jsonKey = null;
dataMemberAttribute = null;

if (ReflectionUtils.GetAttribute(info, typeof(IgnoreDataMemberAttribute)) != null)
return false;
DataMemberAttribute dataMemberAttribute = (DataMemberAttribute)ReflectionUtils.GetAttribute(info, typeof(DataMemberAttribute));
dataMemberAttribute = (DataMemberAttribute)ReflectionUtils.GetAttribute(info, typeof(DataMemberAttribute));
if (dataMemberAttribute == null)
return false;
jsonKey = string.IsNullOrEmpty(dataMemberAttribute.Name) ? info.Name : dataMemberAttribute.Name;
return true;
}
}
Expand Down