Skip to content

Commit

Permalink
Add Json.NET converters for Microsoft.Spatial
Browse files Browse the repository at this point in the history
Resolves Azure#13165
  • Loading branch information
heaths committed Sep 22, 2020
1 parent 723f43f commit e96570c
Show file tree
Hide file tree
Showing 11 changed files with 672 additions and 0 deletions.
12 changes: 12 additions & 0 deletions sdk/search/Azure.Search.Documents.sln
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Azure.Core.TestFramework",
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Azure.Core.Experimental", "..\core\Azure.Core.Experimental\src\Azure.Core.Experimental.csproj", "{32F92309-FF56-4E48-81FF-ADC7318F6DA5}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Spatial.NewtonsoftJson", "Microsoft.Spatial.NewtonsoftJson\src\Microsoft.Spatial.NewtonsoftJson.csproj", "{C2A3FECA-1696-4EDD-ABA1-30F1E3D6CE1D}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Spatial.NewtonsoftJson.Tests", "Microsoft.Spatial.NewtonsoftJson\tests\Microsoft.Spatial.NewtonsoftJson.Tests.csproj", "{622772C8-A2CB-4F8B-82EB-2A46BCC21DAE}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand All @@ -39,6 +43,14 @@ Global
{32F92309-FF56-4E48-81FF-ADC7318F6DA5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{32F92309-FF56-4E48-81FF-ADC7318F6DA5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{32F92309-FF56-4E48-81FF-ADC7318F6DA5}.Release|Any CPU.Build.0 = Release|Any CPU
{C2A3FECA-1696-4EDD-ABA1-30F1E3D6CE1D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C2A3FECA-1696-4EDD-ABA1-30F1E3D6CE1D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C2A3FECA-1696-4EDD-ABA1-30F1E3D6CE1D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C2A3FECA-1696-4EDD-ABA1-30F1E3D6CE1D}.Release|Any CPU.Build.0 = Release|Any CPU
{622772C8-A2CB-4F8B-82EB-2A46BCC21DAE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{622772C8-A2CB-4F8B-82EB-2A46BCC21DAE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{622772C8-A2CB-4F8B-82EB-2A46BCC21DAE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{622772C8-A2CB-4F8B-82EB-2A46BCC21DAE}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down
3 changes: 3 additions & 0 deletions sdk/search/Microsoft.Spatial.NewtonsoftJson/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Release History

## 1.0.0-preview.1 (Unreleased)
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<IsClientLibrary>true</IsClientLibrary>
</PropertyGroup>

<Import Project="..\Directory.Build.props" />
</Project>
49 changes: 49 additions & 0 deletions sdk/search/Microsoft.Spatial.NewtonsoftJson/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# Newtonsoft.Json implementation for Microsoft.Spatial library for .NET

Microsoft.Spatial contains classes and methods that facilitate geography and geometry spatial operations.
This library contains implementations dependent on Newtonsoft.Json, aka JSON.NET, for use with Microsoft.Spatial.

## Getting started

TODO

### Install the package

TODO

### Prerequisites

TODO

### Authenticate the client

TODO

## Key concepts

TODO

## Examples

TODO

## Troubleshooting

TODO

## Next steps

TODO

## Contributing

This project welcomes contributions and suggestions. Most contributions require you to agree to a Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us the rights to use your contribution. For details, visit <https://cla.microsoft.com>.

When you submit a pull request, a CLA-bot will automatically determine whether you need to provide a CLA and decorate the PR appropriately (e.g., label, comment). Simply follow the instructions provided by the bot. You will only need to do this once across all repos using our CLA.

This project has adopted the [Microsoft Open Source Code of Conduct][code_of_conduct]. For more information see the [Code of Conduct FAQ][code_of_conduct_faq] or contact opencode@microsoft.com with any additional questions or comments.

![Impressions](https://azure-sdk-impressions.azurewebsites.net/api/impressions/azure-sdk-for-net%2Fsdk%2Fcore%2FMicrosoft.Azure.Core.NewtonsoftJson%2FREADME.png)

[code_of_conduct]: https://opensource.microsoft.com/codeofconduct
[code_of_conduct_faq]: https://opensource.microsoft.com/codeofconduct/faq/
185 changes: 185 additions & 0 deletions sdk/search/Microsoft.Spatial.NewtonsoftJson/src/GeoJsonExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System.Collections.Generic;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

namespace Microsoft.Spatial.Serialization
{
/// <summary>
/// Defines extension methods for various JSON.NET types that make it easier to recognize and read Geo-JSON.
/// </summary>
internal static class GeoJsonExtensions
{
private const string Coordinates = "coordinates";
private const string Crs = "crs";
private const string Name = "name";
private const string Point = "Point";
private const string Properties = "properties";
private const string Type = "type";
private const string WorldGeodeticSystem1984 = "EPSG:4326"; // See https://epsg.io/4326

private static readonly IEnumerable<string> CrsOnly = new[] { Crs };
private static readonly IEnumerable<string> NameOnly = new[] { Name };
private static readonly IEnumerable<string> TypeAndCoordinates = new[] { Type, Coordinates };
private static readonly IEnumerable<string> TypeAndProperties = new[] { Type, Properties };

/// <summary>
/// Determines whether the given <see cref="JObject" /> is a valid Geo-JSON point.
/// </summary>
/// <param name="obj">The JSON object to test.</param>
/// <returns><c>true</c> if the JSON object is not null and is a valid Geo-JSON point, <c>false</c> otherwise.</returns>
public static bool IsGeoJsonPoint(this JObject obj) =>
obj?.IsValid(
requiredProperties: TypeAndCoordinates,
isPropertyValid: property =>
{
switch (property.Name)
{
case Type:
return property.Value.IsString(Point);
case Coordinates:
return AreCoordinates(property.Value);
case Crs:
return property.Value is JObject possibleCrs && IsCrs(possibleCrs);
default:
return false;
}
}) ?? false;

/// <summary>
/// Reads a Geo-JSON point into a <see cref="GeographyPoint" /> instance, or throws
/// <see cref="JsonSerializationException" /> if the reader is not positioned on the
/// beginning of a valid Geo-JSON point.
/// </summary>
/// <param name="reader">The JSON reader from which to read a Geo-JSON point.</param>
/// <returns>A <see cref="GeographyPoint" /> instance.</returns>
public static GeographyPoint ReadGeoJsonPoint(this JsonReader reader)
{
// Check for null first.
if (reader.TokenType == JsonToken.Null)
{
return null;
}

GeographyPoint result = null;

reader.ReadObject(
requiredProperties: TypeAndCoordinates,
optionalProperties: CrsOnly,
readProperty: (r, propertyName) =>
{
switch (propertyName)
{
case Type:
r.ExpectAndAdvance(JsonToken.String, Point);
break;
case Coordinates:
result = ReadCoordinates(r);
break;
case Crs:
ReadCrs(r);
break;
}
});

return result;
}

/// <summary>
/// Writes a <see cref="GeographyPoint" /> instance as Geo-JSON format.
/// </summary>
/// <param name="writer">The JSON writer to which to write the Geo-JSON point.</param>
/// <param name="point">The <see cref="GeographyPoint" /> instance to write.</param>
public static void WriteGeoJsonPoint(this JsonWriter writer, GeographyPoint point)
{
writer.WriteStartObject();
writer.WritePropertyName(Type);
writer.WriteValue(Point);
writer.WritePropertyName(Coordinates);
writer.WriteStartArray();
writer.WriteValue(point.Longitude);
writer.WriteValue(point.Latitude);
writer.WriteEndArray();
writer.WriteEndObject();
}

private static bool IsCrsProperties(JObject possibleProperties) =>
possibleProperties.IsValid(
requiredProperties: NameOnly,
isPropertyValid: property => property.Name == Name && property.Value.IsString(WorldGeodeticSystem1984));

private static void ReadCrsProperties(JsonReader propertiesReader) =>
propertiesReader.ReadObjectAndAdvance(
requiredProperties: NameOnly,
readProperty: (r, _) => r.ExpectAndAdvance(JsonToken.String, WorldGeodeticSystem1984));

private static bool IsCrs(JObject possibleCrs) =>
possibleCrs.IsValid(
requiredProperties: TypeAndProperties,
isPropertyValid: property =>
{
switch (property.Name)
{
case Type:
return property.Value.IsString(Name);
case Properties:
return property.Value is JObject possibleProperties && IsCrsProperties(possibleProperties);
default:
return false;
}
});

private static void ReadCrs(JsonReader crsReader) =>
crsReader.ReadObjectAndAdvance(
requiredProperties: TypeAndProperties,
readProperty: (r, propertyName) =>
{
switch (propertyName)
{
case Type:
r.ExpectAndAdvance(JsonToken.String, Name);
break;
case Properties:
ReadCrsProperties(r);
break;
}
});

private static bool AreCoordinates(JToken possibleCoordinates) =>
possibleCoordinates is JArray array && array.Count == 2 && array[0].IsNumber() && array[1].IsNumber();

private static GeographyPoint ReadCoordinates(JsonReader coordinatesReader)
{
coordinatesReader.ExpectAndAdvance(JsonToken.StartArray);

double ReadFloatOrInt()
{
switch (coordinatesReader.TokenType)
{
case JsonToken.Integer:
return coordinatesReader.ExpectAndAdvance<long>(JsonToken.Integer);

// Treat all other cases as Float and let ExpectAndAdvance() handle any errors.
default:
return coordinatesReader.ExpectAndAdvance<double>(JsonToken.Float);
}
}

double longitude = ReadFloatOrInt();
double latitude = ReadFloatOrInt();

coordinatesReader.ExpectAndAdvance(JsonToken.EndArray);
return GeographyPoint.Create(latitude, longitude);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System;
using Newtonsoft.Json;

namespace Microsoft.Spatial.Serialization
{
/// <summary>
/// Converts between <see cref="GeographyPoint" /> objects and Geo-JSON points.
/// </summary>
public class GeographyPointConverter : JsonConverter
{
/// <inheritdoc/>
public override bool CanConvert(Type objectType) =>
typeof(GeographyPoint).IsAssignableFrom(objectType);

/// <inheritdoc/>
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) =>
reader.ReadGeoJsonPoint();

/// <inheritdoc/>
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) =>
writer.WriteGeoJsonPoint((GeographyPoint)value);
}
}
Loading

0 comments on commit e96570c

Please sign in to comment.