Skip to content

Commit 80e2d79

Browse files
Update Feature constructor definition to use interface instead of concrete class
Feature defines a constructor with IGeometryObject, Dictionary<string, object>, string types. If an IDictionary<string, object> is provided it will use another Feature constructor defined with IGeometryObject, object, string and will use reflection to create a Dictionary implementation. When this constructor is used with an IDictionary<string, object> a Runtime Exception is thrown with the message: Parameter count mismatch. The first constructor should be updated to follow the Liskov Substitution Principle (https://en.wikipedia.org/wiki/Liskov_substitution_principle) to prevent this undesired behavior: > Substitutability is a principle in object-oriented programming stating that, in a computer program, if S is a subtype of T, then objects of type T may be replaced with objects of type S (i.e. an object of type T may be substituted with any object of a subtype S) without altering any of the desirable properties of the program (correctness, task performed, etc.). Unit tests were added to verify that the behavior does not change when the interface is used instead of the concrete implementation. Resolves: #116
1 parent 53e12d6 commit 80e2d79

File tree

4 files changed

+298
-4
lines changed

4 files changed

+298
-4
lines changed

src/GeoJSON.Net.Tests/Feature/FeatureTests.cs

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,28 @@ public void Can_Serialize_MultiPolygon_Feature()
167167
JsonAssert.AreEqual(expectedJson, actualJson);
168168
}
169169

170+
[Test]
171+
public void Can_Serialize_Dictionary_Subclass()
172+
{
173+
var properties =
174+
new TestFeaturePropertyDictionary()
175+
{
176+
BooleanProperty = true,
177+
DoubleProperty = 1.2345d,
178+
EnumProperty = TestFeatureEnum.Value1,
179+
IntProperty = -1,
180+
StringProperty = "Hello, GeoJSON !"
181+
};
182+
183+
Net.Feature.Feature feature = new Net.Feature.Feature(new Point(new Position(10, 10)), properties);
184+
185+
var expectedJson = this.GetExpectedJson();
186+
var actualJson = JsonConvert.SerializeObject(feature);
187+
188+
Assert.False(string.IsNullOrEmpty(expectedJson));
189+
JsonAssert.AreEqual(expectedJson, actualJson);
190+
}
191+
170192
[Test]
171193
public void Ctor_Can_Add_Properties_Using_Object()
172194
{
@@ -187,6 +209,31 @@ public void Ctor_Can_Add_Properties_Using_Object()
187209
Assert.AreEqual(feature.Properties.Count, 6);
188210
}
189211

212+
[Test]
213+
public void Ctor_Can_Add_Properties_Using_Object_Inheriting_Dictionary()
214+
{
215+
int expectedProperties = 6;
216+
217+
var properties = new TestFeaturePropertyDictionary()
218+
{
219+
BooleanProperty = true,
220+
DateTimeProperty = DateTime.Now,
221+
DoubleProperty = 1.2345d,
222+
EnumProperty = TestFeatureEnum.Value1,
223+
IntProperty = -1,
224+
StringProperty = "Hello, GeoJSON !"
225+
};
226+
227+
Net.Feature.Feature feature = new Net.Feature.Feature(new Point(new Position(10, 10)), properties);
228+
229+
Assert.IsNotNull(feature.Properties);
230+
Assert.IsTrue(feature.Properties.Count > 1);
231+
Assert.AreEqual(
232+
feature.Properties.Count,
233+
expectedProperties,
234+
$"Expected: {expectedProperties} Actual: {feature.Properties.Count}");
235+
}
236+
190237
[Test]
191238
public void Ctor_Creates_Properties_Collection_When_Passed_Null_Proper_Object()
192239
{
@@ -437,7 +484,7 @@ private IGeometryObject GetGeometry()
437484
return multiLine;
438485
}
439486

440-
public static Dictionary<string, object> GetPropertiesInRandomOrder()
487+
public static IDictionary<string, object> GetPropertiesInRandomOrder()
441488
{
442489
var properties = new Dictionary<string, object>()
443490
{
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{
2+
"type":"Feature",
3+
"geometry":{
4+
"type":"Point",
5+
"coordinates":[
6+
10.0,
7+
10.0
8+
]
9+
},
10+
"properties":{
11+
"BooleanProperty":true,
12+
"DoubleProperty":1.2345,
13+
"EnumProperty":1,
14+
"IntProperty":-1,
15+
"StringProperty":"Hello, GeoJSON !"
16+
}
17+
}
Lines changed: 230 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,230 @@
1+
namespace GeoJSON.Net.Tests.Feature
2+
{
3+
using System;
4+
using System.Collections;
5+
using System.Collections.Generic;
6+
7+
/// <summary>
8+
/// The Test Property Dictionary object.
9+
/// </summary>
10+
internal class TestFeaturePropertyDictionary : IDictionary<string, object>
11+
{
12+
/// <summary>
13+
/// The internal dictionary this implementation is wrapping for testing purposes.
14+
/// </summary>
15+
private readonly IDictionary<string, object> internalDictionary;
16+
17+
/// <summary>
18+
/// Initializes a new instance of the <see cref="TestFeaturePropertyDictionary"/> class.
19+
/// </summary>
20+
public TestFeaturePropertyDictionary()
21+
{
22+
this.internalDictionary = new Dictionary<string, object>();
23+
}
24+
25+
public bool BooleanProperty
26+
{
27+
get
28+
{
29+
return this.GetKeyOrDefault<bool>(nameof(this.BooleanProperty));
30+
}
31+
32+
set
33+
{
34+
this.internalDictionary[nameof(this.BooleanProperty)] = value;
35+
}
36+
}
37+
38+
public DateTime DateTimeProperty
39+
{
40+
get
41+
{
42+
return this.GetKeyOrDefault<DateTime>(nameof(this.DateTimeProperty));
43+
}
44+
45+
set
46+
{
47+
this.internalDictionary[nameof(this.DateTimeProperty)] = value;
48+
}
49+
}
50+
51+
public double DoubleProperty
52+
{
53+
get
54+
{
55+
return this.GetKeyOrDefault<double>(nameof(this.DoubleProperty));
56+
}
57+
58+
set
59+
{
60+
this.internalDictionary[nameof(this.DoubleProperty)] = value;
61+
}
62+
}
63+
64+
public TestFeatureEnum EnumProperty
65+
{
66+
get
67+
{
68+
return this.GetKeyOrDefault<TestFeatureEnum>(nameof(this.EnumProperty));
69+
}
70+
71+
set
72+
{
73+
this.internalDictionary[nameof(this.EnumProperty)] = value;
74+
}
75+
}
76+
77+
public int IntProperty
78+
{
79+
get
80+
{
81+
return this.GetKeyOrDefault<int>(nameof(this.IntProperty));
82+
}
83+
84+
set
85+
{
86+
this.internalDictionary[nameof(this.IntProperty)] = value;
87+
}
88+
}
89+
90+
public string StringProperty
91+
{
92+
get
93+
{
94+
return this.GetKeyOrDefault<string>(nameof(this.StringProperty));
95+
}
96+
97+
set
98+
{
99+
this.internalDictionary[nameof(this.StringProperty)] = value;
100+
}
101+
}
102+
103+
/// <inheritdoc/>
104+
public int Count
105+
{
106+
get
107+
{
108+
return this.internalDictionary.Count;
109+
}
110+
}
111+
112+
/// <inheritdoc/>
113+
public bool IsReadOnly
114+
{
115+
get
116+
{
117+
return this.internalDictionary.IsReadOnly;
118+
}
119+
}
120+
121+
/// <inheritdoc/>
122+
public ICollection<string> Keys
123+
{
124+
get
125+
{
126+
return this.internalDictionary.Keys;
127+
}
128+
}
129+
130+
/// <inheritdoc/>
131+
public ICollection<object> Values
132+
{
133+
get
134+
{
135+
return this.internalDictionary.Values;
136+
}
137+
}
138+
139+
/// <inheritdoc/>
140+
public object this[string key]
141+
{
142+
get
143+
{
144+
return this.internalDictionary[key];
145+
}
146+
147+
set
148+
{
149+
this.internalDictionary[key] = value;
150+
}
151+
}
152+
153+
/// <inheritdoc/>
154+
public IEnumerator<KeyValuePair<string, object>> GetEnumerator()
155+
{
156+
return this.internalDictionary.GetEnumerator();
157+
}
158+
159+
/// <inheritdoc/>
160+
IEnumerator IEnumerable.GetEnumerator()
161+
{
162+
return this.GetEnumerator();
163+
}
164+
165+
/// <inheritdoc/>
166+
public void Add(KeyValuePair<string, object> item)
167+
{
168+
this.internalDictionary.Add(item);
169+
}
170+
171+
/// <inheritdoc/>
172+
public void Clear()
173+
{
174+
this.internalDictionary.Clear();
175+
}
176+
177+
/// <inheritdoc/>
178+
public bool Contains(KeyValuePair<string, object> item)
179+
{
180+
return this.internalDictionary.Contains(item);
181+
}
182+
183+
/// <inheritdoc/>
184+
public void CopyTo(KeyValuePair<string, object>[] array, int arrayIndex)
185+
{
186+
this.internalDictionary.CopyTo(array, arrayIndex);
187+
}
188+
189+
/// <inheritdoc/>
190+
public bool Remove(KeyValuePair<string, object> item)
191+
{
192+
return this.internalDictionary.Remove(item);
193+
}
194+
195+
/// <inheritdoc/>
196+
public bool ContainsKey(string key)
197+
{
198+
return this.internalDictionary.ContainsKey(key);
199+
}
200+
201+
/// <inheritdoc/>
202+
public void Add(string key, object value)
203+
{
204+
this.internalDictionary.Add(key, value);
205+
}
206+
207+
/// <inheritdoc/>
208+
public bool Remove(string key)
209+
{
210+
return this.internalDictionary.Remove(key);
211+
}
212+
213+
/// <inheritdoc/>
214+
public bool TryGetValue(string key, out object value)
215+
{
216+
return this.internalDictionary.TryGetValue(key, out value);
217+
}
218+
219+
private T GetKeyOrDefault<T>(string keyName)
220+
{
221+
object value;
222+
if (this.TryGetValue(keyName, out value))
223+
{
224+
return (T)value;
225+
}
226+
227+
return default(T);
228+
}
229+
}
230+
}

src/GeoJSON.Net/Feature/Feature.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ public override int GetHashCode()
105105
public class Feature : Feature<IGeometryObject>
106106
{
107107
[JsonConstructor]
108-
public Feature(IGeometryObject geometry, Dictionary<string, object> properties = null, string id = null)
108+
public Feature(IGeometryObject geometry, IDictionary<string, object> properties = null, string id = null)
109109
: base(geometry, properties, id)
110110
{
111111
}
@@ -122,7 +122,7 @@ public Feature(IGeometryObject geometry, object properties, string id = null)
122122
/// </summary>
123123
/// <remarks>Returns correctly typed Geometry property</remarks>
124124
/// <typeparam name="TGeometry"></typeparam>
125-
public class Feature<TGeometry> : Feature<TGeometry, Dictionary<string, object>>, IEquatable<Feature<TGeometry>> where TGeometry : IGeometryObject
125+
public class Feature<TGeometry> : Feature<TGeometry, IDictionary<string, object>>, IEquatable<Feature<TGeometry>> where TGeometry : IGeometryObject
126126
{
127127

128128
/// <summary>
@@ -132,7 +132,7 @@ public class Feature<TGeometry> : Feature<TGeometry, Dictionary<string, object>>
132132
/// <param name="properties">The properties.</param>
133133
/// <param name="id">The (optional) identifier.</param>
134134
[JsonConstructor]
135-
public Feature(TGeometry geometry, Dictionary<string, object> properties = null, string id = null)
135+
public Feature(TGeometry geometry, IDictionary<string, object> properties = null, string id = null)
136136
: base(geometry, properties ?? new Dictionary<string, object>(), id)
137137
{
138138
}

0 commit comments

Comments
 (0)