diff --git a/src/SimpleJson.Tests/DataContractTests/DataContractSampleClassess.cs b/src/SimpleJson.Tests/DataContractTests/DataContractSampleClassess.cs index eff39e2..ae2092f 100644 --- a/src/SimpleJson.Tests/DataContractTests/DataContractSampleClassess.cs +++ b/src/SimpleJson.Tests/DataContractTests/DataContractSampleClassess.cs @@ -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 diff --git a/src/SimpleJson.Tests/DataContractTests/EmitDefaultValueSerializeTests.cs b/src/SimpleJson.Tests/DataContractTests/EmitDefaultValueSerializeTests.cs new file mode 100644 index 0000000..fe92ea8 --- /dev/null +++ b/src/SimpleJson.Tests/DataContractTests/EmitDefaultValueSerializeTests.cs @@ -0,0 +1,74 @@ +//----------------------------------------------------------------------- +// +// 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. +// +// Nathan Totten (ntotten.com), Jim Zimmerman (jimzimmerman.com) and Prabir Shrestha (prabir.me) +// https://github.com/facebook-csharp-sdk/simple-json +//----------------------------------------------------------------------- + +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")); + } + } +} \ No newline at end of file diff --git a/src/SimpleJson.Tests/SimpleJson.Tests.csproj b/src/SimpleJson.Tests/SimpleJson.Tests.csproj index eafe9c8..5fb663f 100644 --- a/src/SimpleJson.Tests/SimpleJson.Tests.csproj +++ b/src/SimpleJson.Tests/SimpleJson.Tests.csproj @@ -52,6 +52,7 @@ + diff --git a/src/SimpleJson/SimpleJson.cs b/src/SimpleJson/SimpleJson.cs index 2ab9742..26c704e 100644 --- a/src/SimpleJson/SimpleJson.cs +++ b/src/SimpleJson/SimpleJson.cs @@ -1244,9 +1244,12 @@ interface IJsonSerializerStrategy #endif class PocoJsonSerializerStrategy : IJsonSerializerStrategy { + public delegate bool EmitPredicate(object value); + internal IDictionary ConstructorCache; internal IDictionary> GetCache; internal IDictionary>> SetCache; + protected internal IDictionary> EmitPredicateCache; internal static readonly Type[] EmptyTypes = new Type[0]; internal static readonly Type[] ArrayConstructorParameterTypes = new Type[] { typeof(int) }; @@ -1270,6 +1273,11 @@ protected virtual string MapClrMemberNameToJsonFieldName(string clrPropertyName) return clrPropertyName; } + internal virtual IDictionary EmitPredicateFactory(Type type) + { + return null; + } + internal virtual ReflectionUtils.ConstructorDelegate ContructorDelegateFactory(Type key) { return ReflectionUtils.GetContructor(key, key.IsArray ? ArrayConstructorParameterTypes : EmptyTypes); @@ -1504,10 +1512,21 @@ protected virtual bool TrySerializeUnknownTypes(object input, out object output) return false; IDictionary obj = new JsonObject(); IDictionary getters = GetCache[type]; + IDictionary emitPredicate = EmitPredicateCache == null ? null : EmitPredicateCache[type]; foreach (KeyValuePair 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; @@ -1527,28 +1546,95 @@ public DataContractJsonSerializerStrategy() { GetCache = new ReflectionUtils.ThreadSafeDictionary>(GetterValueFactory); SetCache = new ReflectionUtils.ThreadSafeDictionary>>(SetterValueFactory); + EmitPredicateCache = new ReflectionUtils.ThreadSafeDictionary>(EmitPredicateFactory); + } + + /// + /// Helper method to supply the name of the json key, either from the DataMemberAttribute or from the MemberInfo + /// + /// DataMemberAttribute + /// + /// string with the name in the Json + private string JsonKey(DataMemberAttribute dataMemberAttribute, MemberInfo memberInfo) + { + return string.IsNullOrEmpty(dataMemberAttribute.Name) ? memberInfo.Name : dataMemberAttribute.Name; + } + + /// + /// 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 + /// + /// Type to create a default for + /// Default for type + 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; + } + + /// + /// 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 + /// + /// + internal override IDictionary EmitPredicateFactory(Type type) + { + IDictionary result = new Dictionary(); + 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 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 result = new Dictionary(); 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; } @@ -1559,34 +1645,41 @@ public DataContractJsonSerializerStrategy() if (!hasDataContract) return base.SetterValueFactory(type); string jsonKey; + DataMemberAttribute dataMemberAttribute; IDictionary> result = new Dictionary>(); 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(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(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; } }