diff --git a/src/ServiceStack.Text/Common/WriteType.cs b/src/ServiceStack.Text/Common/WriteType.cs index d1e1bf7c0..92cd2e631 100644 --- a/src/ServiceStack.Text/Common/WriteType.cs +++ b/src/ServiceStack.Text/Common/WriteType.cs @@ -13,6 +13,7 @@ using System; using System.Collections; using System.IO; +using System.Linq; using System.Reflection; using ServiceStack.Text.Json; using ServiceStack.Text.Reflection; @@ -123,7 +124,7 @@ private static bool Init() { var propertyInfo = propertyInfos[i]; - string propertyName, propertyNameCLSFriendly, propertyNameLowercaseUnderscore; + string propertyName, propertyNameCLSFriendly, propertyNameLowercaseUnderscore, propertyReflectedName; if (isDataContract) { @@ -133,12 +134,14 @@ private static bool Init() propertyName = dcsDataMember.Name ?? propertyInfo.Name; propertyNameCLSFriendly = dcsDataMember.Name ?? propertyName.ToCamelCase(); propertyNameLowercaseUnderscore = dcsDataMember.Name ?? propertyName.ToLowercaseUnderscore(); + propertyReflectedName = dcsDataMember.Name ?? propertyInfo.ReflectedType.Name; } else { propertyName = propertyInfo.Name; propertyNameCLSFriendly = propertyName.ToCamelCase(); propertyNameLowercaseUnderscore = propertyName.ToLowercaseUnderscore(); + propertyReflectedName = propertyInfo.ReflectedType.Name; } var propertyType = propertyInfo.PropertyType; @@ -149,6 +152,7 @@ private static bool Init() PropertyWriters[i] = new TypePropertyWriter ( propertyName, + propertyReflectedName, propertyNameCLSFriendly, propertyNameLowercaseUnderscore, propertyInfo.GetValueGetter(), @@ -164,6 +168,7 @@ private static bool Init() string propertyName = fieldInfo.Name; string propertyNameCLSFriendly = propertyName.ToCamelCase(); string propertyNameLowercaseUnderscore = propertyName.ToLowercaseUnderscore(); + string propertyReflectedName = fieldInfo.ReflectedType.Name; var propertyType = fieldInfo.FieldType; var suppressDefaultValue = propertyType.IsValueType() && JsConfig.HasSerializeFn.Contains(propertyType) @@ -173,6 +178,7 @@ private static bool Init() PropertyWriters[i + propertyNamesLength] = new TypePropertyWriter ( propertyName, + propertyReflectedName, propertyNameCLSFriendly, propertyNameLowercaseUnderscore, fieldInfo.GetValueGetter(), @@ -198,16 +204,20 @@ internal string PropertyName } } internal readonly string propertyName; + internal readonly string propertyReflectedName; + internal readonly string propertyCombinedNameUpper; internal readonly string propertyNameCLSFriendly; internal readonly string propertyNameLowercaseUnderscore; internal readonly Func GetterFn; internal readonly WriteObjectDelegate WriteFn; internal readonly object DefaultValue; - public TypePropertyWriter(string propertyName, string propertyNameCLSFriendly, string propertyNameLowercaseUnderscore, + public TypePropertyWriter(string propertyName, string propertyReflectedName, string propertyNameCLSFriendly, string propertyNameLowercaseUnderscore, Func getterFn, WriteObjectDelegate writeFn, object defaultValue) { this.propertyName = propertyName; + this.propertyReflectedName = propertyReflectedName; + this.propertyCombinedNameUpper = propertyReflectedName.ToUpper() + "." + propertyName.ToUpper(); this.propertyNameCLSFriendly = propertyNameCLSFriendly; this.propertyNameLowercaseUnderscore = propertyNameLowercaseUnderscore; this.GetterFn = getterFn; @@ -274,6 +284,8 @@ public static void WriteProperties(TextWriter writer, object value) if (PropertyWriters != null) { var len = PropertyWriters.Length; + var exclude = JsConfig.ExcludePropertyReferences ?? new string[0]; + exclude = Array.ConvertAll(exclude, x => x.ToUpper()); for (int index = 0; index < len; index++) { var propertyWriter = PropertyWriters[index]; @@ -285,6 +297,8 @@ public static void WriteProperties(TextWriter writer, object value) || (propertyWriter.DefaultValue != null && propertyWriter.DefaultValue.Equals(propertyValue))) && !Serializer.IncludeNullValues) continue; + if (exclude.Any() && exclude.Contains(propertyWriter.propertyCombinedNameUpper)) continue; + if (i++ > 0) writer.Write(JsWriter.ItemSeperator); diff --git a/src/ServiceStack.Text/JsConfig.cs b/src/ServiceStack.Text/JsConfig.cs index a97458aa9..605a2bb9d 100644 --- a/src/ServiceStack.Text/JsConfig.cs +++ b/src/ServiceStack.Text/JsConfig.cs @@ -50,7 +50,8 @@ public static JsConfigScope With( bool? escapeUnicode = null, bool? includePublicFields = null, int? maxDepth = null, - EmptyCtorFactoryDelegate modelFactory = null) + EmptyCtorFactoryDelegate modelFactory = null, + string[] excludePropertyReferences = null) { return new JsConfigScope { ConvertObjectTypesIntoStringDictionary = convertObjectTypesIntoStringDictionary ?? sConvertObjectTypesIntoStringDictionary, @@ -75,6 +76,7 @@ public static JsConfigScope With( IncludePublicFields = includePublicFields ?? sIncludePublicFields, MaxDepth = maxDepth ?? sMaxDepth, ModelFactory = modelFactory ?? ModelFactory, + ExcludePropertyReferences = excludePropertyReferences ?? sExcludePropertyReferences }; } @@ -523,6 +525,16 @@ public static EmptyCtorFactoryDelegate ModelFactory } } + private static string[] sExcludePropertyReferences; + public static string[] ExcludePropertyReferences { + get { + return (JsConfigScope.Current != null ? JsConfigScope.Current.ExcludePropertyReferences : null) + ?? sExcludePropertyReferences; + } + set { + if (sExcludePropertyReferences != null) sExcludePropertyReferences = value; + } + } public static void Reset() { @@ -556,6 +568,7 @@ public static void Reset() HasSerializeFn = new HashSet(); TreatValueAsRefTypes = new HashSet { typeof(KeyValuePair<,>) }; PropertyConvention = JsonPropertyConvention.ExactMatch; + sExcludePropertyReferences = null; } public static void Reset(Type cachesForType) diff --git a/src/ServiceStack.Text/JsConfigScope.cs b/src/ServiceStack.Text/JsConfigScope.cs index 1814608da..4b581a700 100644 --- a/src/ServiceStack.Text/JsConfigScope.cs +++ b/src/ServiceStack.Text/JsConfigScope.cs @@ -79,5 +79,6 @@ public void Dispose() public bool? IncludePublicFields { get; set; } public int? MaxDepth { get; set; } public EmptyCtorFactoryDelegate ModelFactory { get; set; } + public string[] ExcludePropertyReferences { get; set; } } } diff --git a/tests/ServiceStack.Text.Tests/AdhocModelTests.cs b/tests/ServiceStack.Text.Tests/AdhocModelTests.cs index bfb25256a..30d4c58f7 100644 --- a/tests/ServiceStack.Text.Tests/AdhocModelTests.cs +++ b/tests/ServiceStack.Text.Tests/AdhocModelTests.cs @@ -334,6 +334,95 @@ public void Can_exclude_properties() Assert.That(dto.ToJsv(), Is.EqualTo("{Key:Value}")); } + [Test] + public void Can_exclude_properties_scoped() { + var dto = new Exclude {Id = 1, Key = "Value"}; + using (var config = JsConfig.BeginScope()) { + config.ExcludePropertyReferences = new[] {"Exclude.Id"}; + Assert.That(dto.ToJson(), Is.EqualTo("{\"Key\":\"Value\"}")); + Assert.That(dto.ToJsv(), Is.EqualTo("{Key:Value}")); + } + } + + public class IncludeExclude { + public int Id { get; set; } + public string Name { get; set; } + public Exclude Obj { get; set; } + } + + [Test] + public void Can_include_nested_only() { + var dto = new IncludeExclude { + Id = 1234, + Name = "TEST", + Obj = new Exclude { + Id = 1, + Key = "Value" + } + }; + + using (var config = JsConfig.BeginScope()) { + config.ExcludePropertyReferences = new[] { "Exclude.Id", "IncludeExclude.Id", "IncludeExclude.Name" }; + Assert.That(dto.ToJson(), Is.EqualTo("{\"Obj\":{\"Key\":\"Value\"}}")); + Assert.That(dto.ToJsv(), Is.EqualTo("{Obj:{Key:Value}}")); + } + Assert.That(JsConfig.ExcludePropertyReferences, Is.EqualTo(null)); + + } + + [Test] + public void Exclude_all_nested() + { + var dto = new IncludeExclude + { + Id = 1234, + Name = "TEST", + Obj = new Exclude + { + Id = 1, + Key = "Value" + } + }; + + using (var config = JsConfig.BeginScope()) + { + config.ExcludePropertyReferences = new[] { "Exclude.Id", "Exclude.Key" }; + Assert.AreEqual(2, config.ExcludePropertyReferences.Length); + + var actual = dto.ToJson(); + Assert.That(actual, Is.EqualTo("{\"Id\":1234,\"Name\":\"TEST\",\"Obj\":{}}")); + Assert.That(dto.ToJsv(), Is.EqualTo("{Id:1234,Name:TEST,Obj:{}}")); + } + } + + public class ExcludeList { + public int Id { get; set; } + public List Excludes { get; set; } + } + + [Test] + public void Exclude_List_Scope() { + var dto = new ExcludeList { + Id = 1234, + Excludes = new List() { + new Exclude { + Id = 2345, + Key = "Value" + }, + new Exclude { + Id = 3456, + Key = "Value" + } + } + }; + using (var config = JsConfig.BeginScope()) + { + config.ExcludePropertyReferences = new[] { "ExcludeList.Id", "Exclude.Id" }; + Assert.That(dto.ToJson(), Is.EqualTo("{\"Excludes\":[{\"Key\":\"Value\"},{\"Key\":\"Value\"}]}")); + Assert.That(dto.ToJsv(), Is.EqualTo("{Excludes:[{Key:Value},{Key:Value}]}")); + } + } + public class HasIndex { public int Id { get; set; }