Skip to content

Commit

Permalink
Merge branch 'develop'
Browse files Browse the repository at this point in the history
- (Json.NET): replaced usage of the serializer context
- (Json.NET): treat "coordinates":null and "geometries":null as empty
- (STJ): emit "coordinates":[] instead of "coordinates":null for empty, for better interoperability
  • Loading branch information
airbreather committed Oct 16, 2020
2 parents 5a5fa15 + 18ee7ec commit 106db44
Show file tree
Hide file tree
Showing 9 changed files with 163 additions and 21 deletions.
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,9 @@ UpgradeLog*.htm
# Microsoft Fakes
FakesAssemblies/

# JetBrains Rider
.idea/

# Data generated from unit tests
NetTopologySuite.Samples.Shapefiles/ZMtest.shp
NetTopologySuite.Samples.Shapefiles/ZMtest.shx
Expand All @@ -202,4 +205,4 @@ NetTopologySuite.Samples.Shapefiles/tmp*.shp
NetTopologySuite.Samples.Shapefiles/tmp*.shx
NetTopologySuite.Samples.Shapefiles/tmp*.dbf
NetTopologySuite.Samples.Shapefiles/test_arcview.shp
NetTopologySuite.Samples.Shapefiles/test_buffer.shp
NetTopologySuite.Samples.Shapefiles/test_buffer.shp
50 changes: 50 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,52 @@
# GeoJSON

GeoJSON IO module for NTS.

## GeoJSON4STJ Usage

This is the package for System.Text.Json serialization and deserialization.

### ASP.NET Core Example

Add the `System.Text.Json.Serializer.JsonConverterFactory`, `GeoJsonConverterFactory`, to the `JsonSerializerOptions` when you configure your controllers, MVC, etc in the `ConfigureServices` method of your `Startup.cs` class.

```csharp
public void ConfigureServices(IServiceCollection services) {
services.AddControllers()
.AddJsonOptions(options => {
options.JsonSerializerOptions.Converters.Add(new NetTopologySuite.IO.Converters.GeoJsonConverterFactory());
});
}
````

## GeoJSON Usage

**GeoJSON to `Geometry`**:

```c#
var geoJson = "{\"type\":\"Point\",\"coordinates\":[0.0,0.0]}";
Geometry geometry;

var serializer = GeoJsonSerializer.Create();
using (var stringReader = new StringReader(geoJson))
using (var jsonReader = new JsonTextReader(stringReader))
{
geometry = serializer.Deserialize<Geometry>(jsonReader);
}
```

**`Geometry` to GeoJSON**:

```c#
var geometry = new Point(0, 0);
string geoJson;

var serializer = GeoJsonSerializer.Create();
using (var stringWriter = new StringWriter())
using (var jsonWriter = new JsonTextWriter(stringWriter))
{
serializer.Serialize(jsonWriter, geometry);
geoJson = stringWriter.ToString();
}
```

Original file line number Diff line number Diff line change
Expand Up @@ -148,20 +148,10 @@ private static object InternalReadJson(JsonReader reader, JsonSerializer seriali
// Advance reader
reader.Read();
reader.SkipComments();

IAttributesTable attributesTable = null;
if (!innerObject && serializer.Context.Context is IFeature feature)
{
attributesTable = feature.Attributes;
}
var attributesTable = new AttributesTable();

if (reader.TokenType != JsonToken.Null)
{
if (attributesTable is null)
{
attributesTable = new AttributesTable();
}

while (reader.TokenType == JsonToken.PropertyName)
{
string attributeName = (string)reader.Value;
Expand Down
21 changes: 17 additions & 4 deletions src/NetTopologySuite.IO.GeoJSON/Converters/FeatureConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -173,10 +173,23 @@ public override object ReadJson(JsonReader reader, Type objectType, object exist
throw new ArgumentException("Expected token '{' not found.");
}

var context = serializer.Context;
serializer.Context = new StreamingContext(serializer.Context.State, feature);
feature.Attributes = serializer.Deserialize<AttributesTable>(reader);
serializer.Context = context;
var attributes = serializer.Deserialize<AttributesTable>(reader);

if (feature.Attributes is null)
{
feature.Attributes = attributes;
}
else
{
foreach (var attribute in attributes)
{
if (!feature.Attributes.Exists(attribute.Key))
{
feature.Attributes.Add(attribute.Key, attribute.Value);
}
}
}

if (reader.TokenType != JsonToken.EndObject)
{
throw new ArgumentException("Expected token '}' not found.");
Expand Down
41 changes: 39 additions & 2 deletions src/NetTopologySuite.IO.GeoJSON/Converters/GeometryConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -348,12 +348,28 @@ private Geometry ParseGeometry(JsonReader reader, JsonSerializer serializer)
case "geometries":
//only geom collection has "geometries"
reader.ReadOrThrow(); //read past start array tag
coords = ParseGeomCollection(reader, serializer);
if (reader.TokenType == JsonToken.Null)
{
reader.ReadOrThrow();
}
else
{
coords = ParseGeomCollection(reader, serializer);
}

break;

case "coordinates":
reader.ReadOrThrow(); //read past start array tag
coords = ReadCoordinates(reader);
if (reader.TokenType == JsonToken.Null)
{
reader.ReadOrThrow();
}
else
{
coords = ReadCoordinates(reader);
}

break;

case "bbox":
Expand All @@ -379,24 +395,45 @@ private Geometry ParseGeometry(JsonReader reader, JsonSerializer serializer)

switch (geometryType)
{
case GeoJsonObjectType.Point when coords is null:
return _factory.CreatePoint();

case GeoJsonObjectType.Point:
return CreatePoint(reader, coords);

case GeoJsonObjectType.MultiPoint when coords is null:
return _factory.CreateMultiPoint();

case GeoJsonObjectType.MultiPoint:
return _factory.CreateMultiPoint(coords.Select(obj => CreatePoint(reader, (List<object>)obj)).ToArray());

case GeoJsonObjectType.LineString when coords is null:
return _factory.CreateLineString();

case GeoJsonObjectType.LineString:
return CreateLineString(reader, coords);

case GeoJsonObjectType.MultiLineString when coords is null:
return _factory.CreateMultiLineString();

case GeoJsonObjectType.MultiLineString:
return _factory.CreateMultiLineString(coords.Select(obj => CreateLineString(reader, (List<object>)obj)).ToArray());

case GeoJsonObjectType.Polygon when coords is null:
return _factory.CreatePolygon();

case GeoJsonObjectType.Polygon:
return CreatePolygon(reader, coords);

case GeoJsonObjectType.MultiPolygon when coords is null:
return _factory.CreateMultiPolygon();

case GeoJsonObjectType.MultiPolygon:
return _factory.CreateMultiPolygon(coords.Select(obj => CreatePolygon(reader, (List<object>)obj)).ToArray());

case GeoJsonObjectType.GeometryCollection when coords is null:
return _factory.CreateGeometryCollection();

case GeoJsonObjectType.GeometryCollection:
return _factory.CreateGeometryCollection(coords.Cast<Geometry>().ToArray());

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<!-- MAJOR, MINOR, and PATCH are defined according to SemVer 2.0.0. -->
<NtsMajorVersion Condition=" '$(NtsMajorVersion)' == '' ">2</NtsMajorVersion>
<NtsMinorVersion Condition=" '$(NtsMinorVersion)' == '' ">0</NtsMinorVersion>
<NtsPatchVersion Condition=" '$(NtsPatchVersion)' == '' ">3</NtsPatchVersion>
<NtsPatchVersion Condition=" '$(NtsPatchVersion)' == '' ">4</NtsPatchVersion>
</PropertyGroup>

<Import Project="Sdk.props" Sdk="Microsoft.NET.Sdk" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,8 @@ public override void Write(Utf8JsonWriter writer, Geometry value, JsonSerializer
}
else if (value.IsEmpty)
{
writer.WriteNull("coordinates");
writer.WriteStartArray("coordinates");
writer.WriteEndArray();
}
else
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<!-- MAJOR, MINOR, and PATCH are defined according to SemVer 2.0.0. -->
<NtsMajorVersion Condition=" '$(NtsMajorVersion)' == '' ">2</NtsMajorVersion>
<NtsMinorVersion Condition=" '$(NtsMinorVersion)' == '' ">1</NtsMinorVersion>
<NtsPatchVersion Condition=" '$(NtsPatchVersion)' == '' ">0</NtsPatchVersion>
<NtsPatchVersion Condition=" '$(NtsPatchVersion)' == '' ">1</NtsPatchVersion>
</PropertyGroup>

<Import Project="Sdk.props" Sdk="Microsoft.NET.Sdk" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -441,6 +441,54 @@ public void TestDeserializeObjectInsideProperties()
Assert.That(mc.ValueInt32, Is.EqualTo(17));
Assert.That(mc.ValueDouble, Is.EqualTo(Math.PI));
}

[GeoJsonIssueNumber(59)]
[Test]
public void TestGeoJsonWithNestedObjectsInProperties()
{
const string geojson =
@"{
""type"": ""Feature"",
""geometry"": {
""type"": ""Point"",
""coordinates"": [1, 2]
},
""properties"": {
""complex"": {
""a"": [""b"", ""c""],
""d"": [""e"", ""f""]
}
}
}
}";

Feature f = null;
Assert.That(() => f = new GeoJsonReader().Read<Feature>(geojson), Throws.Nothing);
Assert.That(f, Is.Not.Null);
Assert.That(f.Attributes["complex"], Is.InstanceOf<AttributesTable>());
var innerTable = f.Attributes["complex"] as AttributesTable;
Assert.That(innerTable["a"], Is.InstanceOf<List<object>>());
Assert.That(innerTable["d"], Is.InstanceOf<List<object>>());
}

[GeoJsonIssueNumber(65)]
[TestCase("Point")]
[TestCase("LineString")]
[TestCase("Polygon")]
[TestCase("MultiPoint")]
[TestCase("MultiLineString")]
[TestCase("MultiPolygon")]
[TestCase("GeometryCollection")]
public void TestGeoJsonWithNullCoordinatesOrGeometries(string geometryType)
{
string tag = geometryType == "GeometryCollection" ? "geometries" : "coordinates";
string geojson = $"{{\"type\": \"{geometryType}\", \"{tag}\": null}}";

Geometry g = null;
Assert.That(() => g = new GeoJsonReader().Read<Geometry>(geojson), Throws.Nothing);
Assert.That(g, Is.Not.Null);
Assert.That(g.IsEmpty);
}
}

class MyClass
Expand Down

0 comments on commit 106db44

Please sign in to comment.