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

Commit 768d86b

Browse files
committed
Normalize behavior of parsing nullable booleans
1 parent 36a2cc9 commit 768d86b

3 files changed

Lines changed: 94 additions & 17 deletions

File tree

src/ServiceStack.Text/Common/DeserializeBuiltin.cs

Lines changed: 4 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -37,14 +37,7 @@ private static ParseStringSpanDelegate GetParseStringSpanFn()
3737
switch (typeCode)
3838
{
3939
case TypeCode.Boolean:
40-
//Lots of kids like to use '1', HTML checkboxes use 'on' as a soft convention
41-
return value =>
42-
value.Length == 1 ?
43-
value[0] == '1'
44-
: value.Length == 2 ?
45-
value[0] == 'o' && value[1] == 'n' :
46-
value.ParseBoolean();
47-
40+
return value => value.ParseBoolean();
4841
case TypeCode.SByte:
4942
return SignedInteger<sbyte>.ParseObject;
5043
case TypeCode.Byte:
@@ -87,14 +80,9 @@ private static ParseStringSpanDelegate GetParseStringSpanFn()
8780
switch (typeCode)
8881
{
8982
case TypeCode.Boolean:
90-
return value => value.IsNullOrEmpty() ?
91-
(bool?)null
92-
: value.Length == 1 ?
93-
value[0] == '1'
94-
: value.Length == 2 ?
95-
value[0] == 'o' && value[1] == 'n' :
96-
value.ParseBoolean();
97-
83+
return value => value.IsNullOrEmpty()
84+
? (bool?)null
85+
: value.ParseBoolean();
9886
case TypeCode.SByte:
9987
return SignedInteger<sbyte>.ParseNullableObject;
10088
case TypeCode.Byte:

src/ServiceStack.Text/StringSpanExtensions.cs

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,42 @@ public static ReadOnlySpan<char> FromCsvField(this ReadOnlySpan<char> text)
7070
}
7171

7272
[MethodImpl(MethodImplOptions.AggressiveInlining)]
73-
public static bool ParseBoolean(this ReadOnlySpan<char> value) => MemoryProvider.Instance.ParseBoolean(value);
73+
public static bool ParseBoolean(this ReadOnlySpan<char> value)
74+
{
75+
//Lots of kids like to use '1', HTML checkboxes use 'on' as a soft convention
76+
switch (value.Length)
77+
{
78+
case 0:
79+
return false;
80+
case 1:
81+
switch (value[0])
82+
{
83+
case '1':
84+
case 't':
85+
case 'T':
86+
case 'y':
87+
case 'Y':
88+
return true;
89+
case '0':
90+
case 'f':
91+
case 'F':
92+
case 'n':
93+
case 'N':
94+
return false;
95+
}
96+
break;
97+
case 2:
98+
if (value[0] == 'o' && value[1] == 'n')
99+
return true;
100+
break;
101+
case 3:
102+
if (value[0] == 'o' && value[1] == 'f' && value[1] == 'f')
103+
return false;
104+
break;
105+
}
106+
107+
return MemoryProvider.Instance.ParseBoolean(value);
108+
}
74109

75110
public static bool TryParseBoolean(this ReadOnlySpan<char> value, out bool result) =>
76111
MemoryProvider.Instance.TryParseBoolean(value, out result);
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
using NUnit.Framework;
2+
3+
namespace ServiceStack.Text.Tests.Issues
4+
{
5+
public class NullableIssues
6+
{
7+
public class NBoolTest
8+
{
9+
public bool? IsOk {get; set;}
10+
11+
protected bool Equals(NBoolTest other) => IsOk == other.IsOk;
12+
public override bool Equals(object obj)
13+
{
14+
if (ReferenceEquals(null, obj)) return false;
15+
if (ReferenceEquals(this, obj)) return true;
16+
if (obj.GetType() != this.GetType()) return false;
17+
return Equals((NBoolTest) obj);
18+
}
19+
public override int GetHashCode() => IsOk.GetHashCode();
20+
}
21+
22+
[Test]
23+
public void Does_deserialize_nullable_bools()
24+
{
25+
Assert.That("{\"IsOk\": true}".FromJson<NBoolTest>(), Is.EqualTo(new NBoolTest { IsOk = true }));
26+
Assert.That("{\"IsOk\": false}".FromJson<NBoolTest>(), Is.EqualTo(new NBoolTest { IsOk = false }));
27+
Assert.That("{\"IsOk\": \"true\"}".FromJson<NBoolTest>(), Is.EqualTo(new NBoolTest { IsOk = true }));
28+
Assert.That("{\"IsOk\": \"false\"}".FromJson<NBoolTest>(), Is.EqualTo(new NBoolTest { IsOk = false }));
29+
Assert.That("{\"IsOk\": \"True\"}".FromJson<NBoolTest>(), Is.EqualTo(new NBoolTest { IsOk = true }));
30+
Assert.That("{\"IsOk\": \"False\"}".FromJson<NBoolTest>(), Is.EqualTo(new NBoolTest { IsOk = false }));
31+
32+
Assert.That("{\"IsOk\": null}".FromJson<NBoolTest>(), Is.EqualTo(new NBoolTest { IsOk = null }));
33+
}
34+
35+
[Test]
36+
public void Does_deserialize_nullable_bools_conventions()
37+
{
38+
Assert.That("{\"IsOk\": \"t\"}".FromJson<NBoolTest>(), Is.EqualTo(new NBoolTest { IsOk = true }));
39+
Assert.That("{\"IsOk\": \"f\"}".FromJson<NBoolTest>(), Is.EqualTo(new NBoolTest { IsOk = false }));
40+
Assert.That("{\"IsOk\": \"Y\"}".FromJson<NBoolTest>(), Is.EqualTo(new NBoolTest { IsOk = true }));
41+
Assert.That("{\"IsOk\": \"N\"}".FromJson<NBoolTest>(), Is.EqualTo(new NBoolTest { IsOk = false }));
42+
Assert.That("{\"IsOk\": \"on\"}".FromJson<NBoolTest>(), Is.EqualTo(new NBoolTest { IsOk = true }));
43+
Assert.That("{\"IsOk\": \"off\"}".FromJson<NBoolTest>(), Is.EqualTo(new NBoolTest { IsOk = false }));
44+
}
45+
46+
[Test]
47+
public void Deserialize_nullable_bools_results_in_error()
48+
{
49+
Assert.That("{\"IsOk\": \"tt\"}".FromJson<NBoolTest>(), Is.EqualTo(new NBoolTest { IsOk = null }));
50+
Assert.That("{\"IsOk\": \"fu\"}".FromJson<NBoolTest>(), Is.EqualTo(new NBoolTest { IsOk = null }));
51+
Assert.That("{\"IsOk\": \"eee\"}".FromJson<NBoolTest>(), Is.EqualTo(new NBoolTest { IsOk = null }));
52+
}
53+
}
54+
}

0 commit comments

Comments
 (0)