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

Commit 6562428

Browse files
committed
Merge pull request #362 from pavelsavara/DataMemberOrder
DataMemberAttribute - Order, EmitDefaultValue
2 parents 325d295 + 51d5a43 commit 6562428

File tree

7 files changed

+146
-47
lines changed

7 files changed

+146
-47
lines changed

src/ServiceStack.Text/Common/WriteType.cs

Lines changed: 59 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,11 @@ private static bool Init()
125125
var propertyInfo = propertyInfos[i];
126126

127127
string propertyName, propertyNameCLSFriendly, propertyNameLowercaseUnderscore, propertyReflectedName;
128+
int propertyOrder = -1;
129+
var propertyType = propertyInfo.PropertyType;
130+
var defaultValue = propertyType.GetDefaultValue();
131+
bool propertySuppressDefaultConfig = defaultValue != null && propertyType.IsValueType() && JsConfig.HasSerializeFn.Contains(propertyType);
132+
bool propertySuppressDefaultAttribute = false;
128133

129134
if (isDataContract)
130135
{
@@ -135,6 +140,8 @@ private static bool Init()
135140
propertyNameCLSFriendly = dcsDataMember.Name ?? propertyName.ToCamelCase();
136141
propertyNameLowercaseUnderscore = dcsDataMember.Name ?? propertyName.ToLowercaseUnderscore();
137142
propertyReflectedName = dcsDataMember.Name ?? propertyInfo.ReflectedType.Name;
143+
propertyOrder = dcsDataMember.Order;
144+
propertySuppressDefaultAttribute = !dcsDataMember.EmitDefaultValue;
138145
}
139146
else
140147
{
@@ -144,49 +151,68 @@ private static bool Init()
144151
propertyReflectedName = propertyInfo.ReflectedType.Name;
145152
}
146153

147-
var propertyType = propertyInfo.PropertyType;
148-
var suppressDefaultValue = propertyType.IsValueType() && JsConfig.HasSerializeFn.Contains(propertyType)
149-
? propertyType.GetDefaultValue()
150-
: null;
151154

152155
PropertyWriters[i] = new TypePropertyWriter
153156
(
154157
propertyName,
155158
propertyReflectedName,
156159
propertyNameCLSFriendly,
157160
propertyNameLowercaseUnderscore,
161+
propertyOrder,
162+
propertySuppressDefaultConfig,
163+
propertySuppressDefaultAttribute,
158164
propertyInfo.GetValueGetter<T>(),
159165
Serializer.GetWriteFn(propertyType),
160-
suppressDefaultValue
166+
propertyType.GetDefaultValue()
161167
);
162168
}
163169

164170
for (var i = 0; i < fieldNamesLength; i++)
165171
{
166172
var fieldInfo = fieldInfos[i];
167173

168-
string propertyName = fieldInfo.Name;
169-
string propertyNameCLSFriendly = propertyName.ToCamelCase();
170-
string propertyNameLowercaseUnderscore = propertyName.ToLowercaseUnderscore();
171-
string propertyReflectedName = fieldInfo.ReflectedType.Name;
172-
174+
string propertyName, propertyNameCLSFriendly, propertyNameLowercaseUnderscore, propertyReflectedName;
175+
int propertyOrder = -1;
173176
var propertyType = fieldInfo.FieldType;
174-
var suppressDefaultValue = propertyType.IsValueType() && JsConfig.HasSerializeFn.Contains(propertyType)
175-
? propertyType.GetDefaultValue()
176-
: null;
177+
var defaultValue = propertyType.GetDefaultValue();
178+
bool propertySuppressDefaultConfig = defaultValue != null && propertyType.IsValueType() && JsConfig.HasSerializeFn.Contains(propertyType);
179+
bool propertySuppressDefaultAttribute = false;
180+
181+
if (isDataContract)
182+
{
183+
var dcsDataMember = fieldInfo.GetDataMember();
184+
if (dcsDataMember == null) continue;
185+
186+
propertyName = dcsDataMember.Name ?? fieldInfo.Name;
187+
propertyNameCLSFriendly = dcsDataMember.Name ?? propertyName.ToCamelCase();
188+
propertyNameLowercaseUnderscore = dcsDataMember.Name ?? propertyName.ToLowercaseUnderscore();
189+
propertyReflectedName = dcsDataMember.Name ?? fieldInfo.ReflectedType.Name;
190+
propertyOrder = dcsDataMember.Order;
191+
propertySuppressDefaultAttribute = !dcsDataMember.EmitDefaultValue;
192+
}
193+
else
194+
{
195+
propertyName = fieldInfo.Name;
196+
propertyNameCLSFriendly = propertyName.ToCamelCase();
197+
propertyNameLowercaseUnderscore = propertyName.ToLowercaseUnderscore();
198+
propertyReflectedName = fieldInfo.ReflectedType.Name;
199+
}
177200

178201
PropertyWriters[i + propertyNamesLength] = new TypePropertyWriter
179202
(
180203
propertyName,
181204
propertyReflectedName,
182205
propertyNameCLSFriendly,
183206
propertyNameLowercaseUnderscore,
207+
propertyOrder,
208+
propertySuppressDefaultConfig,
209+
propertySuppressDefaultAttribute,
184210
fieldInfo.GetValueGetter<T>(),
185211
Serializer.GetWriteFn(propertyType),
186-
suppressDefaultValue
212+
defaultValue
187213
);
188214
}
189-
215+
PropertyWriters = PropertyWriters.OrderBy(x => x.propertyOrder).ToArray();
190216
return true;
191217
}
192218

@@ -204,6 +230,9 @@ internal string PropertyName
204230
}
205231
}
206232
internal readonly string propertyName;
233+
internal readonly int propertyOrder;
234+
internal readonly bool propertySuppressDefaultConfig;
235+
internal readonly bool propertySuppressDefaultAttribute;
207236
internal readonly string propertyReflectedName;
208237
internal readonly string propertyCombinedNameUpper;
209238
internal readonly string propertyNameCLSFriendly;
@@ -212,10 +241,13 @@ internal string PropertyName
212241
internal readonly WriteObjectDelegate WriteFn;
213242
internal readonly object DefaultValue;
214243

215-
public TypePropertyWriter(string propertyName, string propertyReflectedName, string propertyNameCLSFriendly, string propertyNameLowercaseUnderscore,
244+
public TypePropertyWriter(string propertyName, string propertyReflectedName, string propertyNameCLSFriendly, string propertyNameLowercaseUnderscore, int propertyOrder, bool propertySuppressDefaultConfig,bool propertySuppressDefaultAttribute,
216245
Func<T, object> getterFn, WriteObjectDelegate writeFn, object defaultValue)
217246
{
218247
this.propertyName = propertyName;
248+
this.propertyOrder = propertyOrder;
249+
this.propertySuppressDefaultConfig = propertySuppressDefaultConfig;
250+
this.propertySuppressDefaultAttribute = propertySuppressDefaultAttribute;
219251
this.propertyReflectedName = propertyReflectedName;
220252
this.propertyCombinedNameUpper = propertyReflectedName.ToUpper() + "." + propertyName.ToUpper();
221253
this.propertyNameCLSFriendly = propertyNameCLSFriendly;
@@ -293,9 +325,17 @@ public static void WriteProperties(TextWriter writer, object value)
293325
? propertyWriter.GetterFn((T)value)
294326
: null;
295327

296-
if ((propertyValue == null
297-
|| (propertyWriter.DefaultValue != null && propertyWriter.DefaultValue.Equals(propertyValue)))
298-
&& !Serializer.IncludeNullValues) continue;
328+
if (propertyWriter.propertySuppressDefaultAttribute && Equals(propertyWriter.DefaultValue, propertyValue))
329+
{
330+
continue;
331+
}
332+
if ((propertyValue == null
333+
|| (propertyWriter.propertySuppressDefaultConfig && Equals(propertyWriter.DefaultValue, propertyValue)))
334+
&& !Serializer.IncludeNullValues
335+
)
336+
{
337+
continue;
338+
}
299339

300340
if (exclude.Any() && exclude.Contains(propertyWriter.propertyCombinedNameUpper)) continue;
301341

src/ServiceStack.Text/ReflectionExtensions.cs

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -574,6 +574,18 @@ public static DataMemberAttribute GetDataMember(this PropertyInfo pi)
574574
var dataMember = pi.CustomAttributes(typeof(DataMemberAttribute), false)
575575
.FirstOrDefault() as DataMemberAttribute;
576576

577+
#if !SILVERLIGHT && !MONOTOUCH && !XBOX
578+
if (dataMember == null && Env.IsMono)
579+
return pi.GetWeakDataMember();
580+
#endif
581+
return dataMember;
582+
}
583+
584+
public static DataMemberAttribute GetDataMember(this FieldInfo pi)
585+
{
586+
var dataMember = pi.CustomAttributes(typeof(DataMemberAttribute), false)
587+
.FirstOrDefault() as DataMemberAttribute;
588+
577589
#if !SILVERLIGHT && !MONOTOUCH && !XBOX
578590
if (dataMember == null && Env.IsMono)
579591
return pi.GetWeakDataMember();
@@ -632,6 +644,36 @@ public static DataMemberAttribute GetWeakDataMember(this PropertyInfo pi)
632644
}
633645
return null;
634646
}
647+
648+
public static DataMemberAttribute GetWeakDataMember(this FieldInfo pi)
649+
{
650+
var attr = pi.CustomAttributes().FirstOrDefault(x => x.GetType().Name == DataMember);
651+
if (attr != null)
652+
{
653+
var attrType = attr.GetType();
654+
655+
FastMember.TypeAccessor accessor;
656+
lock (typeAccessorMap)
657+
{
658+
if (!typeAccessorMap.TryGetValue(attrType, out accessor))
659+
typeAccessorMap[attrType] = accessor = FastMember.TypeAccessor.Create(attr.GetType());
660+
}
661+
662+
var newAttr = new DataMemberAttribute
663+
{
664+
Name = (string)accessor[attr, "Name"],
665+
EmitDefaultValue = (bool)accessor[attr, "EmitDefaultValue"],
666+
IsRequired = (bool)accessor[attr, "IsRequired"],
667+
};
668+
669+
var order = (int)accessor[attr, "Order"];
670+
if (order >= 0)
671+
newAttr.Order = order; //Throws Exception if set to -1
672+
673+
return newAttr;
674+
}
675+
return null;
676+
}
635677
#endif
636678
}
637679

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ public void Does_serialize_To_CamelCase()
3939
var json = dto.ToJson();
4040

4141
Assert.That(json, Is.EqualTo(
42-
"{\"id\":1,\"imdbId\":\"tt0111161\",\"title\":\"The Shawshank Redemption\",\"rating\":9.2,\"director\":\"Frank Darabont\",\"releaseDate\":\"\\/Date(792979200000)\\/\",\"tagLine\":\"Fear can hold you prisoner. Hope can set you free.\",\"genres\":[\"Crime\",\"Drama\"]}"));
42+
"{\"id\":1,\"title\":\"The Shawshank Redemption\",\"imdbId\":\"tt0111161\",\"rating\":9.2,\"director\":\"Frank Darabont\",\"releaseDate\":\"\\/Date(792979200000)\\/\",\"tagLine\":\"Fear can hold you prisoner. Hope can set you free.\",\"genres\":[\"Crime\",\"Drama\"]}"));
4343

4444
Serialize(dto);
4545
}

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

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
using System.Collections.Generic;
33
using NUnit.Framework;
44
using ServiceStack.Client;
5-
using ServiceStack.Common.Tests.Models;
5+
using ServiceStack.Text.Tests.Support;
66

77
namespace ServiceStack.Text.Tests.JsonTests
88
{
@@ -14,7 +14,7 @@ public class JsonDataContractCompatibilityTests
1414
[Test]
1515
public void Can_serialize_a_movie()
1616
{
17-
const string clientJson = "{\"Id\":\"tt0110912\",\"Title\":\"Pulp Fiction\",\"Rating\":\"8.9\",\"Director\":\"Quentin Tarantino\",\"ReleaseDate\":\"/Date(785635200000)/\",\"TagLine\":\"Girls like me don't make invitations like this to just anyone!\",\"Genres\":[\"Crime\",\"Drama\",\"Thriller\"]}";
17+
const string clientJson = "{\"Id\":\"0110912\",\"ImdbId\":\"tt0111161\",\"Title\":\"Pulp Fiction\",\"Rating\":\"8.9\",\"Director\":\"Quentin Tarantino\",\"ReleaseDate\":\"/Date(785635200000)/\",\"TagLine\":\"Girls like me don't make invitations like this to just anyone!\",\"Genres\":[\"Crime\",\"Drama\",\"Thriller\"]}";
1818
var jsonModel = JsonSerializer.DeserializeFromString<Movie>(clientJson);
1919
var bclJsonModel = BclJsonDataContractDeserializer.Instance.Parse<Movie>(clientJson);
2020

@@ -25,7 +25,24 @@ public void Can_serialize_a_movie()
2525
Console.WriteLine("CLIENT {0}\nSS {1}\nBCL {2}", clientJson, ssJson, wcfJson);
2626

2727
Assert.That(jsonModel, Is.EqualTo(bclJsonModel));
28-
}
28+
Assert.That(ssJson, Is.EqualTo(wcfJson));
29+
}
30+
31+
[Test]
32+
public void Respects_EmitDefaultValue()
33+
{
34+
using (var x = JsConfig.BeginScope())
35+
{
36+
x.IncludeNullValues = true;
37+
38+
var jsonModel = new Movie { Genres = null };
39+
40+
var ssJson = JsonSerializer.SerializeToString(jsonModel);
41+
var wcfJson = BclJsonDataContractSerializer.Instance.Parse(jsonModel);
42+
43+
Assert.That(ssJson, Is.EqualTo(wcfJson));
44+
}
45+
}
2946

3047
[Test]
3148
public void Can_deserialize_empty_type()

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ public void Does_serialize_To_lowercase_underscore()
3939
var json = dto.ToJson();
4040

4141
Assert.That(json, Is.EqualTo(
42-
"{\"id\":1,\"imdb_id\":\"tt0111161\",\"title\":\"The Shawshank Redemption\",\"rating\":9.2,\"director\":\"Frank Darabont\",\"release_date\":\"\\/Date(792979200000)\\/\",\"tag_line\":\"Fear can hold you prisoner. Hope can set you free.\",\"genres\":[\"Crime\",\"Drama\"]}"));
42+
"{\"id\":1,\"title\":\"The Shawshank Redemption\",\"imdb_id\":\"tt0111161\",\"rating\":9.2,\"director\":\"Frank Darabont\",\"release_date\":\"\\/Date(792979200000)\\/\",\"tag_line\":\"Fear can hold you prisoner. Hope can set you free.\",\"genres\":[\"Crime\",\"Drama\"]}"));
4343

4444
Serialize(dto);
4545
}

tests/ServiceStack.Text.Tests/Support/DdnDtos.cs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -448,17 +448,17 @@ public ResponseStatus()
448448
this.Errors = new List<ResponseError>();
449449
}
450450

451-
[DataMember]
451+
[DataMember(EmitDefaultValue = false, IsRequired = false)]
452452
public string ErrorCode { get; set; }
453453

454-
[DataMember]
455-
public string Message { get; set; }
454+
[DataMember(EmitDefaultValue = false, IsRequired = false)]
455+
public string Message { get; set; }
456456

457-
[DataMember]
458-
public string StackTrace { get; set; }
457+
[DataMember(EmitDefaultValue = false, IsRequired = false)]
458+
public string StackTrace { get; set; }
459459

460-
[DataMember]
461-
public List<ResponseError> Errors { get; set; }
460+
[DataMember(EmitDefaultValue = false, IsRequired = false)]
461+
public List<ResponseError> Errors { get; set; }
462462

463463

464464
public bool IsSuccess

tests/ServiceStack.Text.Tests/Support/MovieDtos.cs

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -27,30 +27,30 @@ public Movie()
2727
this.Genres = new List<string>();
2828
}
2929

30-
[DataMember]
31-
[AutoIncrement]
30+
[DataMember(EmitDefaultValue = false, IsRequired = false)]
31+
[AutoIncrement]
3232
public int Id { get; set; }
3333

34-
[DataMember]
35-
public string ImdbId { get; set; }
34+
[DataMember(Order = 3, EmitDefaultValue = false, IsRequired = false)]
35+
public string ImdbId { get; set; }
3636

37-
[DataMember]
38-
public string Title { get; set; }
37+
[DataMember(Order = 2, EmitDefaultValue = false, IsRequired = false)]
38+
public string Title { get; set; }
3939

40-
[DataMember]
41-
public decimal Rating { get; set; }
40+
[DataMember(Order = 4, EmitDefaultValue = false, IsRequired = false)]
41+
public decimal Rating { get; set; }
4242

43-
[DataMember]
44-
public string Director { get; set; }
43+
[DataMember(Order = 5, EmitDefaultValue = true, IsRequired = false)]
44+
public string Director { get; set; }
4545

46-
[DataMember]
47-
public DateTime ReleaseDate { get; set; }
46+
[DataMember(Order = 6, EmitDefaultValue = false, IsRequired = false)]
47+
public DateTime ReleaseDate { get; set; }
4848

49-
[DataMember]
50-
public string TagLine { get; set; }
49+
[DataMember(Order = 6, EmitDefaultValue = false, IsRequired = false)]
50+
public string TagLine { get; set; }
5151

52-
[DataMember]
53-
public List<string> Genres { get; set; }
52+
[DataMember(Order = 8, EmitDefaultValue = false, IsRequired = false)]
53+
public List<string> Genres { get; set; }
5454

5555
#region AutoGen ReSharper code, only required by tests
5656
public bool Equals(Movie other)

0 commit comments

Comments
 (0)