Skip to content
This repository was archived by the owner on Dec 24, 2022. It is now read-only.

Commit 981de0f

Browse files
committed
Add ShouldSerialize and OnDeserializing features from #432
1 parent 9a0443a commit 981de0f

File tree

8 files changed

+179
-12
lines changed

8 files changed

+179
-12
lines changed

src/ServiceStack.Text/Common/DeserializeType.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,8 @@ internal static ParseStringDelegate GetParseMethod(TypeConfig typeConfig)
3636
return value => ctorFn();
3737

3838
return typeof(TSerializer) == typeof(Json.JsonTypeSerializer)
39-
? (ParseStringDelegate)(value => DeserializeTypeRefJson.StringToType(type, value, ctorFn, map))
40-
: value => DeserializeTypeRefJsv.StringToType(type, value, ctorFn, map);
39+
? (ParseStringDelegate)(value => DeserializeTypeRefJson.StringToType(typeConfig, value, ctorFn, map))
40+
: value => DeserializeTypeRefJsv.StringToType(typeConfig, value, ctorFn, map);
4141
}
4242

4343
public static object ObjectStringToType(string strType)

src/ServiceStack.Text/Common/DeserializeTypeRefJson.cs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,12 +48,13 @@ internal static class DeserializeTypeRefJson
4848
private static readonly JsonTypeSerializer Serializer = (JsonTypeSerializer)JsonTypeSerializer.Instance;
4949

5050
internal static object StringToType(
51-
Type type,
51+
TypeConfig typeConfig,
5252
string strType,
5353
EmptyCtorDelegate ctorFn,
5454
Dictionary<string, TypeAccessor> typeAccessorMap)
5555
{
5656
var index = 0;
57+
var type = typeConfig.Type;
5758

5859
if (strType == null)
5960
return null;
@@ -140,6 +141,8 @@ internal static object StringToType(
140141
var parseFn = JsonReader.GetParseFn(propType);
141142

142143
var propertyValue = parseFn(propertyValueStr);
144+
if (typeConfig.OnDeserializing != null)
145+
propertyValue = typeConfig.OnDeserializing(instance, propertyName, propertyValue);
143146
typeAccessor.SetProperty(instance, propertyValue);
144147
}
145148

@@ -167,6 +170,8 @@ internal static object StringToType(
167170
try
168171
{
169172
var propertyValue = typeAccessor.GetProperty(propertyValueStr);
173+
if (typeConfig.OnDeserializing != null)
174+
propertyValue = typeConfig.OnDeserializing(instance, propertyName, propertyValue);
170175
typeAccessor.SetProperty(instance, propertyValue);
171176
}
172177
catch (Exception e)
@@ -175,6 +180,11 @@ internal static object StringToType(
175180
else Tracer.Instance.WriteWarning("WARN: failed to set property {0} with: {1}", propertyName, propertyValueStr);
176181
}
177182
}
183+
else if (typeConfig.OnDeserializing != null)
184+
{
185+
// the property is not known by the DTO
186+
typeConfig.OnDeserializing(instance, propertyName, propertyValueStr);
187+
}
178188

179189
//Serializer.EatItemSeperatorOrMapEndChar(strType, ref index);
180190
for (; index < strType.Length; index++) { var c = strType[index]; if (c >= JsonTypeSerializer.WhiteSpaceFlags.Length || !JsonTypeSerializer.WhiteSpaceFlags[c]) break; } //Whitespace inline

src/ServiceStack.Text/Common/DeserializeTypeRefJsv.cs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,13 @@ internal static class DeserializeTypeRefJsv
1212
private static readonly JsvTypeSerializer Serializer = (JsvTypeSerializer)JsvTypeSerializer.Instance;
1313

1414
internal static object StringToType(
15-
Type type,
15+
TypeConfig typeConfig,
1616
string strType,
1717
EmptyCtorDelegate ctorFn,
1818
Dictionary<string, TypeAccessor> typeAccessorMap)
1919
{
2020
var index = 0;
21+
var type = typeConfig.Type;
2122

2223
if (strType == null)
2324
return null;
@@ -100,6 +101,8 @@ internal static object StringToType(
100101
{
101102
var parseFn = Serializer.GetParseFn(propType);
102103
var propertyValue = parseFn(propertyValueStr);
104+
if (typeConfig.OnDeserializing != null)
105+
propertyValue = typeConfig.OnDeserializing(instance, propertyName, propertyValue);
103106
typeAccessor.SetProperty(instance, propertyValue);
104107
}
105108

@@ -120,6 +123,8 @@ internal static object StringToType(
120123
try
121124
{
122125
var propertyValue = typeAccessor.GetProperty(propertyValueStr);
126+
if (typeConfig.OnDeserializing != null)
127+
propertyValue = typeConfig.OnDeserializing(instance, propertyName, propertyValue);
123128
typeAccessor.SetProperty(instance, propertyValue);
124129
}
125130
catch (Exception e)
@@ -128,6 +133,11 @@ internal static object StringToType(
128133
else Tracer.Instance.WriteWarning("WARN: failed to set property {0} with: {1}", propertyName, propertyValueStr);
129134
}
130135
}
136+
else if (typeConfig.OnDeserializing != null)
137+
{
138+
// the property is not known by the DTO
139+
typeConfig.OnDeserializing(instance, propertyName, propertyValueStr);
140+
}
131141

132142
//Serializer.EatItemSeperatorOrMapEndChar(strType, ref index);
133143
if (index != strType.Length) index++;

src/ServiceStack.Text/Common/WriteType.cs

Lines changed: 39 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,14 @@ static Func<T, bool> GetShouldSerializeMethod(MemberInfo member)
108108
return (method == null || method.ReturnType != typeof(bool)) ? null : (Func<T, bool>)method.CreateDelegate(typeof(Func<T, bool>));
109109
}
110110

111+
static Func<T, string, bool?> ShouldSerialize(Type type)
112+
{
113+
var method = type.GetMethod("ShouldSerialize", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, new[] { typeof(string) }, null);
114+
return (method == null || method.ReturnType != typeof(bool?))
115+
? null
116+
: (Func<T, string, bool?>)Delegate.CreateDelegate(typeof(Func<T, string, bool?>), method);
117+
}
118+
111119
private static bool Init()
112120
{
113121
if (!typeof(T).IsClass() && !typeof(T).IsInterface() && !JsConfig.TreatAsRefType(typeof(T))) return false;
@@ -123,6 +131,8 @@ private static bool Init()
123131
return typeof(T).IsDto();
124132
}
125133

134+
var shouldSerializeDynamic = ShouldSerialize(typeof(T));
135+
126136
// NOTE: very limited support for DataContractSerialization (DCS)
127137
// NOT supporting Serializable
128138
// support for DCS is intended for (re)Name of properties and Ignore by NOT having a DataMember present
@@ -173,6 +183,7 @@ private static bool Init()
173183
Serializer.GetWriteFn(propertyType),
174184
propertyType.GetDefaultValue(),
175185
shouldSerialize,
186+
shouldSerializeDynamic,
176187
propertyType.IsEnum()
177188
);
178189
}
@@ -225,6 +236,7 @@ private static bool Init()
225236
Serializer.GetWriteFn(propertyType),
226237
defaultValue,
227238
shouldSerialize,
239+
shouldSerializeDynamic,
228240
propertyType.IsEnum()
229241
);
230242
}
@@ -257,11 +269,15 @@ internal string PropertyName
257269
internal readonly WriteObjectDelegate WriteFn;
258270
internal readonly object DefaultValue;
259271
internal readonly Func<T, bool> shouldSerialize;
272+
internal readonly Func<T, string, bool?> shouldSerializeDynamic;
260273
internal readonly bool isEnum;
261274

262275
public TypePropertyWriter(string propertyName, string propertyDeclaredTypeName, string propertyNameCLSFriendly,
263276
string propertyNameLowercaseUnderscore, int propertyOrder, bool propertySuppressDefaultConfig,bool propertySuppressDefaultAttribute,
264-
Func<T, object> getterFn, WriteObjectDelegate writeFn, object defaultValue, Func<T, bool> shouldSerialize, bool isEnum)
277+
Func<T, object> getterFn, WriteObjectDelegate writeFn, object defaultValue,
278+
Func<T, bool> shouldSerialize,
279+
Func<T,string, bool?> shouldSerializeDynamic,
280+
bool isEnum)
265281
{
266282
this.propertyName = propertyName;
267283
this.propertyOrder = propertyOrder;
@@ -274,6 +290,7 @@ public TypePropertyWriter(string propertyName, string propertyDeclaredTypeName,
274290
this.WriteFn = writeFn;
275291
this.DefaultValue = defaultValue;
276292
this.shouldSerialize = shouldSerialize;
293+
this.shouldSerializeDynamic = shouldSerializeDynamic;
277294
this.isEnum = isEnum;
278295
}
279296

@@ -361,15 +378,31 @@ public static void WriteProperties(TextWriter writer, object value)
361378
if (propertyWriter.shouldSerialize != null && !propertyWriter.shouldSerialize((T)value))
362379
continue;
363380

381+
var dontSkipDefault = false;
382+
if (propertyWriter.shouldSerializeDynamic != null)
383+
{
384+
var shouldSerialize = propertyWriter.shouldSerializeDynamic((T)value, propertyWriter.PropertyName);
385+
if (shouldSerialize.HasValue)
386+
{
387+
if (shouldSerialize.Value)
388+
dontSkipDefault = true;
389+
else
390+
continue;
391+
}
392+
}
393+
364394
var propertyValue = value != null
365395
? propertyWriter.GetterFn((T)value)
366396
: null;
367-
368-
if (!propertyWriter.ShouldWriteProperty(propertyValue))
369-
continue;
370397

371-
if (JsConfig.ExcludePropertyReferences != null
372-
&& JsConfig.ExcludePropertyReferences.Contains(propertyWriter.propertyReferenceName)) continue;
398+
if (!dontSkipDefault)
399+
{
400+
if (!propertyWriter.ShouldWriteProperty(propertyValue))
401+
continue;
402+
403+
if (JsConfig.ExcludePropertyReferences != null
404+
&& JsConfig.ExcludePropertyReferences.Contains(propertyWriter.propertyReferenceName)) continue;
405+
}
373406

374407
if (i++ > 0)
375408
writer.Write(JsWriter.ItemSeperator);

src/ServiceStack.Text/JsConfig.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -858,6 +858,18 @@ public static Func<T, T> OnDeserializedFn
858858
set { onDeserializedFn = value; }
859859
}
860860

861+
public static bool HasDeserialingFn
862+
{
863+
get { return OnDeserializingFn != null; }
864+
}
865+
866+
private static Func<T, string, object, object> onDeserializingFn;
867+
public static Func<T, string, object, object> OnDeserializingFn
868+
{
869+
get { return onDeserializingFn; }
870+
set { onDeserializingFn = value; }
871+
}
872+
861873
/// <summary>
862874
/// Exclude specific properties of this type from being serialized
863875
/// </summary>

src/ServiceStack.Text/ReflectionExtensions.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -667,6 +667,15 @@ public static PropertyInfo[] GetSerializableProperties(this Type type)
667667
.ToArray();
668668
}
669669

670+
public static Func<object, string, object, object> GetOnDeserializing<T>()
671+
{
672+
var method = typeof(T).GetMethod("OnDeserializing", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, new[] { typeof(string), typeof(object) }, null);
673+
if (method == null || method.ReturnType != typeof (object))
674+
return null;
675+
var obj = (Func<T, string, object, object>)Delegate.CreateDelegate(typeof(Func<T, string, object, object>), method);
676+
return (instance, memberName, value) => obj((T)instance, memberName, value);
677+
}
678+
670679
public static FieldInfo[] GetSerializableFields(this Type type)
671680
{
672681
if (type.IsDto())

src/ServiceStack.Text/TypeConfig.cs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ internal class TypeConfig
1010
internal bool EnableAnonymousFieldSetterses;
1111
internal PropertyInfo[] Properties;
1212
internal FieldInfo[] Fields;
13+
internal Func<object, string, object, object> OnDeserializing;
1314
internal bool IsUserType { get; set; }
1415

1516
internal void AssertValidUsage()
@@ -32,7 +33,7 @@ internal TypeConfig(Type type)
3233

3334
public static class TypeConfig<T>
3435
{
35-
private static TypeConfig config;
36+
internal static TypeConfig config;
3637

3738
static TypeConfig Config
3839
{
@@ -68,6 +69,12 @@ static TypeConfig()
6869
config = Init();
6970
}
7071

72+
public static Func<object, string, object, object> OnDeserializing
73+
{
74+
get { return config.OnDeserializing; }
75+
set { config.OnDeserializing = value; }
76+
}
77+
7178
static TypeConfig Init()
7279
{
7380
config = new TypeConfig(typeof(T));
@@ -80,6 +87,11 @@ static TypeConfig Init()
8087
Properties = properties.Where(x => x.GetIndexParameters().Length == 0).ToArray();
8188

8289
Fields = config.Type.GetSerializableFields().ToArray();
90+
91+
if (!JsConfig<T>.HasDeserialingFn)
92+
OnDeserializing = ReflectionExtensions.GetOnDeserializing<T>();
93+
else
94+
config.OnDeserializing = (instance, memberName, value) => JsConfig<T>.OnDeserializingFn((T)instance, memberName, value);
8395

8496
IsUserType = !typeof(T).IsValueType() && typeof(T).Namespace != "System";
8597

tests/ServiceStack.Text.Tests/JsonTests/ConditionalSerializationTests.cs

Lines changed: 82 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
using NUnit.Framework;
1+
using System.Collections.Generic;
2+
using System.Runtime.Serialization;
3+
using NUnit.Framework;
24
using System;
35

46
namespace ServiceStack.Text.Tests.JsonTests
@@ -14,6 +16,7 @@ public void TestSerializeRespected()
1416
string json = JsonSerializer.SerializeToString(obj);
1517
Assert.That(json, Is.StringMatching("{\"X\":\"abc\",\"Z\":\"def\"}"));
1618
}
19+
1720
[Test]
1821
public void TestSerializeRespectedWithInheritance()
1922
{
@@ -22,6 +25,52 @@ public void TestSerializeRespectedWithInheritance()
2225
string json = JsonSerializer.SerializeToString(obj);
2326
Assert.That(json, Is.StringMatching("{\"A\":123,\"C\":456,\"X\":\"abc\",\"Z\":\"def\"}"));
2427
}
28+
29+
[Test]
30+
public void TestSerializeHasAttributeNull()
31+
{
32+
var obj = new ByNameFoo { A = 123, B = 456 };
33+
34+
string json = JsonSerializer.SerializeToString(obj);
35+
Assert.That(json, Is.StringMatching("{\"A\":123,\"B\":456}"));
36+
}
37+
38+
[Test]
39+
public void TestSerializeHasAttributeSet()
40+
{
41+
var obj = new ByNameFoo { A = 123, B = 456, hasAttribute = new HashSet<string> { "A" } };
42+
43+
string json = JsonSerializer.SerializeToString(obj);
44+
Assert.That(json, Is.StringMatching("{\"A\":123"));
45+
46+
var cpy = JsonSerializer.DeserializeFromString<ByNameFoo>(json);
47+
Assert.That(cpy.A, Is.EqualTo(obj.A));
48+
Assert.That(cpy.hasAttribute, Is.EqualTo(obj.hasAttribute));
49+
}
50+
51+
[Test]
52+
public void TestSerializeHasAttributeSetNullValue()
53+
{
54+
var obj = new ByNameFoo { A = 123, B = null, hasAttribute = new HashSet<string> { "B" } };
55+
56+
string json = JsonSerializer.SerializeToString(obj);
57+
Assert.That(json, Is.StringMatching("{\"B\":null"));
58+
59+
var cpy = JsonSerializer.DeserializeFromString<ByNameFoo>(json);
60+
Assert.That(cpy.A, Is.EqualTo(0));
61+
Assert.That(cpy.B, Is.EqualTo(obj.B));
62+
Assert.That(cpy.hasAttribute, Is.EqualTo(obj.hasAttribute));
63+
}
64+
65+
[Test]
66+
public void OnDeserializingMemberUnknown()
67+
{
68+
var cpy = JsonSerializer.DeserializeFromString<ByNameFoo>("{\"B\":1,\"C\":1");
69+
Assert.That(cpy.A, Is.EqualTo(0));
70+
Assert.That(cpy.B, Is.EqualTo(1));
71+
Assert.That(cpy.hasAttribute, Is.EqualTo(new HashSet<string> { "B", "C" }));
72+
}
73+
2574
public class Foo
2675
{
2776
public string X { get; set; } // not conditional
@@ -65,5 +114,37 @@ public bool ShouldSerializeC()
65114
return true;
66115
}
67116
}
117+
118+
[DataContract]
119+
public class ByNameFoo
120+
{
121+
[IgnoreDataMember]
122+
public HashSet<string> hasAttribute;
123+
124+
[DataMember(EmitDefaultValue = false, IsRequired = false)]
125+
public int A { get; set; }
126+
127+
[DataMember(EmitDefaultValue = false, IsRequired = false)]
128+
public int? B { get; set; }
129+
130+
public bool? ShouldSerialize(string fieldName)
131+
{
132+
if (hasAttribute == null)
133+
{
134+
return null;
135+
}
136+
return hasAttribute.Contains(fieldName);
137+
}
138+
139+
public object OnDeserializing(string fieldName, object value)
140+
{
141+
if (hasAttribute == null)
142+
{
143+
hasAttribute = new HashSet<string>();
144+
}
145+
hasAttribute.Add(fieldName);
146+
return value;
147+
}
148+
}
68149
}
69150
}

0 commit comments

Comments
 (0)