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

Commit 9ef2290

Browse files
committed
Fix null bugs and add support for mixed types in IEnumerable
1 parent c42fffb commit 9ef2290

File tree

6 files changed

+233
-4
lines changed

6 files changed

+233
-4
lines changed

src/ServiceStack.Text/Common/WriteLists.cs

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -182,10 +182,22 @@ public static void WriteIEnumerable(TextWriter writer, object oValueCollection)
182182
Type lastType = null;
183183
foreach (var valueItem in valueCollection)
184184
{
185-
if (toStringFn == null || valueItem.GetType() != lastType)
185+
if ((toStringFn == null) || (valueItem != null && valueItem.GetType() != lastType))
186186
{
187-
lastType = valueItem.GetType();
188-
toStringFn = Serializer.GetWriteFn(lastType);
187+
if (valueItem != null)
188+
{
189+
if (valueItem.GetType() != lastType)
190+
{
191+
lastType = valueItem.GetType();
192+
toStringFn = Serializer.GetWriteFn(lastType);
193+
}
194+
}
195+
else
196+
{
197+
// this can happen if the first item in the collection was null
198+
lastType = typeof(object);
199+
toStringFn = Serializer.GetWriteFn(lastType);
200+
}
189201
}
190202

191203
JsWriter.WriteItemSeperatorIfRanOnce(writer, ref ranOnce);

src/ServiceStack.Text/Common/WriteType.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -384,7 +384,7 @@ public static void WriteProperties(TextWriter writer, object value)
384384
{
385385
var propertyWriter = PropertyWriters[index];
386386

387-
if (propertyWriter.shouldSerialize != null && !propertyWriter.shouldSerialize((T)value))
387+
if (value != null && propertyWriter.shouldSerialize != null && !propertyWriter.shouldSerialize((T)value))
388388
continue;
389389

390390
var dontSkipDefault = false;

tests/ServiceStack.Text.Tests/EnumerableTests.cs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,5 +26,36 @@ public void Can_serialize_array_list_of_mixed_types()
2626

2727
Serialize(list);
2828
}
29+
30+
[Test]
31+
public void Can_serialize_array_list_of_mixed_types_with_null()
32+
{
33+
var list = (IEnumerable)new ArrayList {
34+
1.0,
35+
null,
36+
1,
37+
new object(),
38+
"boo",
39+
1,
40+
1.2
41+
};
42+
43+
Serialize(list);
44+
}
45+
46+
[Test]
47+
public void Can_serialize_array_list_of_mixed_types_with_null_on_first_position()
48+
{
49+
var list = (IEnumerable)new ArrayList {
50+
null,
51+
1,
52+
new object(),
53+
"boo",
54+
1,
55+
1.2
56+
};
57+
58+
Serialize(list);
59+
}
2960
}
3061
}

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

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,58 @@ public void Can_Serialize_StringContainerDto()
149149
Assert.That(dst.Action, Is.EqualTo(fromDst.Action));
150150
}
151151

152+
[Test]
153+
public void Can_handle_null_in_Collection_with_ShouldSerialize()
154+
{
155+
var dto = new Parent {
156+
ChildDtosWithShouldSerialize = new List<ChildWithShouldSerialize> {
157+
new ChildWithShouldSerialize { Data = "xx" }, null,
158+
}
159+
};
160+
161+
var json = JsonSerializer.SerializeToString(dto);
162+
Assert.AreEqual(json, "{\"ChildDtosWithShouldSerialize\":[{\"Data\":\"xx\"},{}]}");
163+
}
164+
165+
[Test]
166+
public void Can_handle_null_in_Collection_with_ShouldSerialize_PropertyName()
167+
{
168+
var dto = new Parent {
169+
ChildDtosWithShouldSerializeProperty = new List<ChildDtoWithShouldSerializeForProperty> {
170+
new ChildDtoWithShouldSerializeForProperty {Data = "xx"},
171+
null,
172+
}
173+
};
174+
175+
var json = JsonSerializer.SerializeToString(dto);
176+
Assert.AreEqual(json, "{\"ChildDtosWithShouldSerializeProperty\":[{\"Data\":\"xx\"},{}]}");
177+
}
178+
179+
public class Parent
180+
{
181+
public IList<ChildWithShouldSerialize> ChildDtosWithShouldSerialize { get; set; }
182+
public IList<ChildDtoWithShouldSerializeForProperty> ChildDtosWithShouldSerializeProperty { get; set; }
183+
}
184+
185+
public class ChildWithShouldSerialize
186+
{
187+
protected virtual bool? ShouldSerialize(string fieldName)
188+
{
189+
return true;
190+
}
191+
192+
public string Data { get; set; }
193+
}
194+
195+
public class ChildDtoWithShouldSerializeForProperty
196+
{
197+
public virtual bool ShouldSerializeData()
198+
{
199+
return true;
200+
}
201+
202+
public string Data { get; set; }
203+
}
152204

153205
readonly SimpleObj simple = new SimpleObj
154206
{
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
using System;
2+
using System.Runtime.Serialization;
3+
using NUnit.Framework;
4+
5+
namespace ServiceStack.Text.Tests.JsonTests
6+
{
7+
public class OnDeserializationErrorTests
8+
{
9+
[Test]
10+
public void Invokes_callback_on_protected_setter()
11+
{
12+
string json = @"{""idBadProt"":""value"", ""idGood"":""2"" }";
13+
14+
AssertThatInvalidJsonInvokesExpectedCallback<TestDto>(json, "idBadProt", "value", typeof(int), "Input string was not in a correct format.");
15+
}
16+
17+
[Test]
18+
public void Invokes_callback_on_incorrect_type()
19+
{
20+
string json = @"{""idBad"":""abc"", ""idGood"":""2"" }";
21+
AssertThatInvalidJsonInvokesExpectedCallback<TestDto>(json, "idBad", "abc", typeof(int), "Input string was not in a correct format.");
22+
}
23+
24+
[Test]
25+
public void Invokes_callback_on_incorrect_type_with_data_set()
26+
{
27+
string json = @"{""idBad"":""abc"", ""idGood"":""2"" }";
28+
AssertThatInvalidJsonInvokesExpectedCallback<TestDto>(json, "idBad", "abc", typeof(int), "Input string was not in a correct format.");
29+
}
30+
31+
[Test]
32+
public void Invokes_callback_on_value_out_of_range()
33+
{
34+
string json = @"{""idBad"":""4700000007"", ""idGood"":""2"" }";
35+
AssertThatInvalidJsonInvokesExpectedCallback<TestDto>(json, "idBad", "4700000007", typeof(int), "Value was either too large or too small for an Int32.");
36+
}
37+
38+
[Test]
39+
public void Does_not_invoke_callback_on_valid_data()
40+
{
41+
JsConfig.Reset();
42+
JsConfig.OnDeserializationErrorCallback = (o, s, s1, arg3, arg4) => Assert.Fail("For valida data this should not be invoked");
43+
44+
var json = @"{""idBad"":""2"", ""idGood"":""2"" }";
45+
JsonSerializer.DeserializeFromString(json, typeof(TestDto));
46+
}
47+
48+
[Test]
49+
public void TestReset()
50+
{
51+
JsConfig.Reset();
52+
Assert.IsNull(JsConfig.OnDeserializationErrorCallback);
53+
JsConfig.OnDeserializationErrorCallback = (o, s, s1, arg3, arg4) => { };
54+
Assert.IsNotNull(JsConfig.OnDeserializationErrorCallback);
55+
JsConfig.Reset();
56+
Assert.IsNull(JsConfig.OnDeserializationErrorCallback);
57+
}
58+
59+
[Test]
60+
public void Invokes_callback_deserialization_of_array_with_missing_comma()
61+
{
62+
string json = @"{""Values"": [ { ""Val"": ""a""} { ""Val"": ""b""}] }";
63+
64+
AssertThatInvalidJsonInvokesExpectedCallback<TestDtoWithArray>(json, "Values", @"[ { ""Val"": ""a""} { ""Val"": ""b""}]", typeof(TestChildDto[]), "Type definitions should start with a '{', expecting serialized type 'TestChildDto', got string starting with: Val");
65+
}
66+
67+
[Test]
68+
public void Does_not_invoke_callback_on_valid_data_with_array()
69+
{
70+
JsConfig.Reset();
71+
JsConfig.OnDeserializationErrorCallback = (o, s, s1, arg3, arg4) => Assert.Fail("For valida data this should not be invoked");
72+
73+
var json = @"{""Values"": [ { ""Val"": ""a""}, { ""Val"": ""b""}] }";
74+
JsonSerializer.DeserializeFromString(json, typeof(TestDtoWithArray));
75+
}
76+
77+
[DataContract]
78+
class TestDtoWithArray
79+
{
80+
[DataMember]
81+
public TestChildDto[] Values { get; set; }
82+
}
83+
84+
[DataContract]
85+
class TestChildDto
86+
{
87+
[DataMember]
88+
public string Val { get; set; }
89+
}
90+
91+
[DataContract]
92+
class TestDto
93+
{
94+
[DataMember(Name = "idBadProt")]
95+
public int protId { get; protected set; }
96+
[DataMember(Name = "idGood")]
97+
public int IdGood { get; set; }
98+
[DataMember(Name = "idBad")]
99+
public int IdBad { get; set; }
100+
}
101+
102+
private static void AssertThatInvalidJsonInvokesExpectedCallback<T>(string json, string expectedProperty, string expectedValue, Type expectedType, string expectedExceptionMessage)
103+
{
104+
string property = null, value = null;
105+
Type type = null;
106+
Exception ex = null;
107+
bool callbackInvoked = false;
108+
object deserialized = null;
109+
110+
JsConfig.Reset();
111+
JsConfig.OnDeserializationErrorCallback = (o, s, s1, arg3, arg4) =>
112+
{
113+
deserialized = o;
114+
property = s;
115+
value = s1;
116+
type = arg3;
117+
ex = arg4;
118+
callbackInvoked = true;
119+
};
120+
121+
122+
JsonSerializer.DeserializeFromString(json, typeof(T));
123+
124+
Assert.IsTrue(callbackInvoked, "Callback should be invoked");
125+
Assert.AreEqual(expectedProperty, property);
126+
Assert.AreEqual(expectedValue, value);
127+
Assert.AreEqual(expectedType, type);
128+
Assert.AreEqual(expectedExceptionMessage, ex.Message);
129+
Assert.IsNotNull(deserialized);
130+
Assert.IsInstanceOf<T>(deserialized);
131+
}
132+
}
133+
}

tests/ServiceStack.Text.Tests/ServiceStack.Text.Tests.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,7 @@
181181
<Compile Include="CsvTypeTests.cs" />
182182
<Compile Include="JsonTests\JsonEnumTests.cs" />
183183
<Compile Include="JsonTests\InvalidJsonTests.cs" />
184+
<Compile Include="JsonTests\OnDeserializationErrorTests.cs" />
184185
<Compile Include="JsonTests\TypeInfoTests.cs" />
185186
<Compile Include="PclApiTests.cs" />
186187
<Compile Include="SerializationDelegatePerformanceTests.cs" />

0 commit comments

Comments
 (0)