Skip to content

Commit 21ec570

Browse files
author
Dmitry Kandiner
committed
Added support for numeric Feature.Id values (Issue #35)
Signed-off-by: Dmitry Kandiner <[email protected]>
1 parent 44abb34 commit 21ec570

File tree

7 files changed

+125
-14
lines changed

7 files changed

+125
-14
lines changed

src/GeoJSON.Text.Test.Unit/Feature/FeatureCollectionTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ public void FeatureCollection_Test_IndexOf()
134134
var actualId = actualFeature.Id;
135135
var actualIndex = model.Features.IndexOf(actualFeature);
136136

137-
var expectedId = expectedIds[i];
137+
FeatureId expectedId = expectedIds[i];
138138
var expectedIndex = expectedIndexes[i];
139139

140140
Assert.AreEqual(expectedId, actualId);

src/GeoJSON.Text.Test.Unit/Feature/FeatureTests.cs

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using GeoJSON.Text.Feature;
12
using GeoJSON.Text.Geometry;
23
using NUnit.Framework;
34
using System;
@@ -24,7 +25,26 @@ public void Can_Deserialize_Point_Feature()
2425
Assert.IsTrue(feature.Properties.ContainsKey("name"));
2526
Assert.AreEqual(feature.Properties["name"].ToString(), "Dinagat Islands");
2627

27-
Assert.AreEqual("test-id", feature.Id);
28+
Assert.AreEqual((FeatureId)"test-id", feature.Id);
29+
30+
Assert.AreEqual(GeoJSONObjectType.Point, feature.Geometry.Type);
31+
}
32+
33+
[Test]
34+
public void Can_Deserialize_Point_Feature_With_Numeric_Id()
35+
{
36+
var json = GetExpectedJson();
37+
38+
var feature = JsonSerializer.Deserialize<Text.Feature.Feature>(json);
39+
40+
Assert.IsNotNull(feature);
41+
Assert.IsNotNull(feature.Properties);
42+
Assert.IsTrue(feature.Properties.Any());
43+
44+
Assert.IsTrue(feature.Properties.ContainsKey("name"));
45+
Assert.AreEqual(feature.Properties["name"].ToString(), "Dinagat Islands");
46+
47+
Assert.AreEqual((FeatureId)17, feature.Id);
2848

2949
Assert.AreEqual(GeoJSONObjectType.Point, feature.Geometry.Type);
3050
}
@@ -354,6 +374,32 @@ public void Serialized_And_Deserialized_Feature_Equals_And_Share_HashCode()
354374
Assert_Are_Equal(left, right); // assert id's + properties doesn't influence comparison and hashcode
355375
}
356376

377+
[Test]
378+
public void Serialized_And_Deserialized_Feature_With_Numeric_Id_Equals_And_Share_HashCode()
379+
{
380+
var geometry = GetGeometry();
381+
382+
var leftFeature = new Text.Feature.Feature(geometry, null, 42);
383+
var leftJson = JsonSerializer.Serialize(leftFeature);
384+
var left = JsonSerializer.Deserialize<Text.Feature.Feature>(leftJson);
385+
386+
var rightFeature = new Text.Feature.Feature(geometry, null, 42);
387+
var rightJson = JsonSerializer.Serialize(rightFeature);
388+
var right = JsonSerializer.Deserialize<Text.Feature.Feature>(rightJson);
389+
390+
Assert_Are_Equal(left, right); // assert id's doesn't influence comparison and hashcode
391+
392+
leftFeature = new Text.Feature.Feature(geometry, GetPropertiesInRandomOrder(), uint.MaxValue);
393+
leftJson = JsonSerializer.Serialize(leftFeature);
394+
left = JsonSerializer.Deserialize<Text.Feature.Feature>(leftJson);
395+
396+
rightFeature = new Text.Feature.Feature(geometry, GetPropertiesInRandomOrder(), uint.MaxValue);
397+
rightJson = JsonSerializer.Serialize(rightFeature);
398+
right = JsonSerializer.Deserialize<Text.Feature.Feature>(rightJson);
399+
400+
Assert_Are_Equal(left, right); // assert id's + properties doesn't influence comparison and hashcode
401+
}
402+
357403
[Test]
358404
public void Feature_Equals_Null_Issue94()
359405
{
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"type": "Feature",
3+
"id" : 17,
4+
"geometry": {
5+
"type": "Point",
6+
"coordinates": [125.6, 10.1]
7+
},
8+
"properties": {
9+
"name": "Dinagat Islands"
10+
}
11+
}

src/GeoJSON.Text.Test.Unit/Feature/GenericFeatureTests.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ public void Can_Deserialize_Point_Feature()
2626
string name = feature.Properties["name"].ToString();
2727
Assert.AreEqual("Dinagat Islands", name);
2828

29-
Assert.AreEqual("test-id", feature.Id);
29+
Assert.AreEqual((FeatureId)"test-id", feature.Id);
3030

3131
Assert.AreEqual(GeoJSONObjectType.Point, feature.Geometry.Type);
3232
Assert.AreEqual(125.6, feature.Geometry.Coordinates.Longitude);
@@ -47,7 +47,7 @@ public void Can_Deserialize_LineString_Feature()
4747
Assert.IsTrue(feature.Properties.ContainsKey("name"));
4848
Assert.AreEqual("Dinagat Islands", feature.Properties["name"].ToString());
4949

50-
Assert.AreEqual("test-id", feature.Id);
50+
Assert.AreEqual((FeatureId)"test-id", feature.Id);
5151

5252
Assert.AreEqual(GeoJSONObjectType.LineString, feature.Geometry.Type);
5353

@@ -103,7 +103,7 @@ public void Can_Deserialize_Typed_Point_Feature()
103103
Assert.AreEqual(feature.Properties.Name, "Dinagat Islands");
104104
Assert.AreEqual(feature.Properties.Value, 4.2);
105105

106-
Assert.AreEqual(feature.Id, "test-id");
106+
Assert.AreEqual(feature.Id, (FeatureId)"test-id");
107107

108108
Assert.AreEqual(feature.Geometry.Type, GeoJSONObjectType.Point);
109109
}

src/GeoJSON.Text.Test.Unit/GeoJSON.Text.Test.Unit.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
<ItemGroup>
4040
<EmbeddedResource Include="Feature\FeatureCollectionTests_Can_Deserialize.json" />
4141
<EmbeddedResource Include="Feature\FeatureCollectionTests_Can_DeserializeGeneric.json" />
42+
<EmbeddedResource Include="Feature\FeatureTests_Can_Deserialize_Point_Feature_With_Numeric_Id.json" />
4243
<EmbeddedResource Include="Feature\FeatureTests_Can_Deserialize_Point_Feature.json" />
4344
<EmbeddedResource Include="Feature\FeatureTests_Can_Serialize_Dictionary_Subclass.json" />
4445
<EmbeddedResource Include="Feature\FeatureTests_Can_Serialize_LineString_Feature.json" />
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// Copyright © Joerg Battermann 2014, Matt Hunt 2017
2+
3+
using GeoJSON.Text.Feature;
4+
using System;
5+
using System.Text.Json;
6+
using System.Text.Json.Serialization;
7+
8+
namespace GeoJSON.Text.Converters
9+
{
10+
internal class FeatureIdConverter : JsonConverter<FeatureId>
11+
{
12+
public override FeatureId Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) => reader.TokenType switch
13+
{
14+
JsonTokenType.String => reader.GetString(),
15+
JsonTokenType.Number => reader.TryGetUInt64(out var value) ? value : throw new JsonException(),
16+
_ => throw new JsonException(),
17+
};
18+
19+
public override void Write(Utf8JsonWriter writer, FeatureId value, JsonSerializerOptions options)
20+
{
21+
if (value.IsNumeric) writer.WriteNumberValue(value);
22+
else writer.WriteStringValue(value);
23+
}
24+
}
25+
}

src/GeoJSON.Text/Feature/Feature.cs

Lines changed: 37 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ namespace GeoJSON.Text.Feature
2121
public class Feature<TGeometry, TProps> : GeoJSONObject, IEquatable<Feature<TGeometry, TProps>>
2222
where TGeometry : IGeometryObject
2323
{
24-
private string _id;
24+
private FeatureId _id;
2525
private bool _idHasValue = false;
2626
private TGeometry _geometry;
2727
private bool _geometryHasValue = false;
@@ -33,14 +33,14 @@ public Feature()
3333

3434
}
3535

36-
public Feature(TGeometry geometry, TProps properties, string id = null)
36+
public Feature(TGeometry geometry, TProps properties, FeatureId id = null)
3737
{
3838
Geometry = geometry;
3939
Properties = properties;
4040
Id = id;
4141
}
4242

43-
public Feature(IGeometryObject geometry, TProps properties, string id = null)
43+
public Feature(IGeometryObject geometry, TProps properties, FeatureId id = null)
4444
{
4545
Geometry = (TGeometry)geometry;
4646
Properties = properties;
@@ -53,7 +53,8 @@ public Feature(IGeometryObject geometry, TProps properties, string id = null)
5353

5454
[JsonPropertyName( "id")]
5555
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
56-
public string Id {
56+
[JsonConverter(typeof(FeatureIdConverter))]
57+
public FeatureId Id {
5758
get
5859
{
5960
return _id;
@@ -195,12 +196,12 @@ public Feature()
195196

196197
}
197198

198-
public Feature(IGeometryObject geometry, IDictionary<string, object> properties = null, string id = null)
199+
public Feature(IGeometryObject geometry, IDictionary<string, object> properties = null, FeatureId id = null)
199200
: base(geometry, properties, id)
200201
{
201202
}
202203

203-
public Feature(IGeometryObject geometry, object properties, string id = null)
204+
public Feature(IGeometryObject geometry, object properties, FeatureId id = null)
204205
: base(geometry, properties, id)
205206
{
206207
}
@@ -225,7 +226,7 @@ public Feature()
225226
/// <param name="geometry">The Geometry Object.</param>
226227
/// <param name="properties">The properties.</param>
227228
/// <param name="id">The (optional) identifier.</param>
228-
public Feature(TGeometry geometry, IDictionary<string, object> properties = null, string id = null)
229+
public Feature(TGeometry geometry, IDictionary<string, object> properties = null, FeatureId id = null)
229230
: base(geometry, properties ?? new Dictionary<string, object>(), id)
230231
{
231232
}
@@ -236,7 +237,7 @@ public Feature(TGeometry geometry, IDictionary<string, object> properties = null
236237
/// <param name="geometry">The Geometry Object.</param>
237238
/// <param name="properties">The properties.</param>
238239
/// <param name="id">The (optional) identifier.</param>
239-
public Feature(IGeometryObject geometry, IDictionary<string, object> properties = null, string id = null)
240+
public Feature(IGeometryObject geometry, IDictionary<string, object> properties = null, FeatureId id = null)
240241
: base((TGeometry)geometry, properties ?? new Dictionary<string, object>(), id)
241242
{
242243
}
@@ -250,7 +251,7 @@ public Feature(IGeometryObject geometry, IDictionary<string, object> properties
250251
/// properties
251252
/// </param>
252253
/// <param name="id">The (optional) identifier.</param>
253-
public Feature(TGeometry geometry, object properties, string id = null)
254+
public Feature(TGeometry geometry, object properties, FeatureId id = null)
254255
: this(geometry, GetDictionaryOfPublicProperties(properties), id)
255256
{
256257
}
@@ -312,5 +313,32 @@ public override int GetHashCode()
312313
{
313314
return !(left?.Equals(right) ?? right is null);
314315
}
316+
}
317+
318+
public sealed class FeatureId : IEquatable<FeatureId>
319+
{
320+
private readonly string _strId;
321+
private readonly ulong? _numId;
322+
323+
private FeatureId(string str, ulong? num) => (_strId, _numId) = (str, num);
324+
325+
public static implicit operator FeatureId(string str) => new(str, null);
326+
public static implicit operator FeatureId(ulong num) => new(null, num);
327+
328+
public bool IsNumeric => _numId.HasValue;
329+
public bool IsString => !IsNumeric;
330+
331+
public static implicit operator string(FeatureId id) => id.IsString ? id._strId : throw new InvalidCastException();
332+
public static implicit operator ulong(FeatureId id) => id.IsNumeric ? id._numId.Value : throw new InvalidCastException();
333+
334+
public override int GetHashCode() => (_strId.GetHashCode() * 397) ^ _numId.GetHashCode();
335+
336+
public bool Equals(FeatureId other) => _strId == other._strId && _numId == other._numId;
337+
338+
public override bool Equals(object obj) => obj is FeatureId id && Equals(id);
339+
340+
public static bool operator ==(FeatureId left, FeatureId right) => left.Equals(right);
341+
342+
public static bool operator !=(FeatureId left, FeatureId right) => !(left == right);
315343
}
316344
}

0 commit comments

Comments
 (0)