/// <summary> /// Accepts the GeoJson coordinates property, and iterates the top level array to determine /// which of the four schemas the coordinates match. /// </summary> /// <param name="reader">A Utf8JsonReader positioned at the GeoJson coordinates property.</param> /// <returns>A reader which will parse out correct parser based on the schema detected.</returns> /// <exception cref="JsonException"></exception> public static GeoJsonCoordinateReader Create(ref Utf8JsonReader reader) { GeoJsonCoordinateReader result; reader.Expect(JsonTokenType.StartArray); int detectedLevel = 0; // How many nested StartArray tokens we find identifies which of the four schemas we're dealing with for (; reader.Read() && reader.TokenType == JsonTokenType.StartArray; detectedLevel++) { if (detectedLevel == 4) { throw new JsonException($"Deserialization failed. GeoJson property '{GeoJsonConstants.CoordinatesPropertyName}' does not contain recognizable GeoJson."); } } reader.Expect(JsonTokenType.Number); switch (detectedLevel) { case 0: result = new LevelZeroGeoJsonCoordinateReader(); break; case 1: result = new LevelOneGeoJsonCoordinateReader(); break; case 2: result = new LevelTwoGeoJsonCoordinateReader(); break; default: result = new LevelThreeGeoJsonCoordinateReader(); break; } result.Process(ref reader); return(result); }
/// <summary> /// Called when a GeometryCollection is either the top level type, or when a GeometryCollection has been nested inside /// another GeometryCollection (which is legal, but apparently discouraged.) /// </summary> /// <param name="reader">A Utf8JsonReader positioned at the beginning of the geometries array.</param> /// <returns>The list of Geographies which were extracted.</returns> private List <Geography> ProcessGeometryCollection(ref Utf8JsonReader reader) { List <Geography> result = new List <Geography>(); reader.Expect(JsonTokenType.StartArray); while (reader.Read() && reader.TokenType != JsonTokenType.EndArray) { Geography geography = ProcessSingleGeography(ref reader); result.Add(geography); } return(result); }
/// <summary> /// Some GeoJson documents define a single geography, e.g. Point and MultiPolygon. GeometryCollection on the other /// hand contains a list of Geographies specified not in the coordinates property, but in the geometries property. /// This methodwill be called at the top level by Read(), and in the case of a GeometryCollection, once for every /// entry in its geometries array. /// </summary> /// <param name="reader"></param> /// <returns></returns> /// <exception cref="JsonException"></exception> private Geography ProcessSingleGeography(ref Utf8JsonReader reader) { string type = null; GeoJsonCoordinateReader geoJsonCoordinateReader = null; List <Geography> geographies = null; reader.Expect(JsonTokenType.StartObject); while (reader.Read() && reader.TokenType != JsonTokenType.EndObject) { reader.Expect(JsonTokenType.PropertyName); string propertyName = reader.GetString(); reader.Read(); if (string.Equals(GeoJsonConstants.TypePropertyName, propertyName, StringComparison.Ordinal)) { reader.Expect(JsonTokenType.String); type = reader.GetString(); } else if (string.Equals(GeoJsonConstants.CoordinatesPropertyName, propertyName, StringComparison.Ordinal)) { geoJsonCoordinateReader = GeoJsonCoordinateReader.Create(ref reader); } else if (string.Equals(GeoJsonConstants.GeometriesPropertyName, propertyName, StringComparison.Ordinal)) { geographies = ProcessGeometryCollection(ref reader); } else { reader.Skip(); } } if (type == null) { throw new JsonException($"Deserialization failed. Required GeoJson property '{GeoJsonConstants.TypePropertyName}' not found."); } if (!TypeNames.Contains(type)) { string valid = TypeNames.FirstOrDefault(z => z.Equals(type, StringComparison.OrdinalIgnoreCase)); if (valid != null) { throw new JsonException($"Deserialization failed. GeoJson property '{GeoJsonConstants.TypePropertyName}' values are case sensitive. Use '{valid}' instead."); } throw new JsonException($"Deserialization failed. GeoJson property '{GeoJsonConstants.TypePropertyName}' contains an invalid value: '{type}'."); } if (geoJsonCoordinateReader != null) { return(geoJsonCoordinateReader.GetGeography(type)); } else if (geographies != null) { return(GeographyFactory.Collection().Create(geographies)); } else { if (type == GeoJsonConstants.GeometryCollectionTypeName) { throw new JsonException($"Deserialization failed. Required GeoJson property '{GeoJsonConstants.GeometriesPropertyName}' not found."); } else { throw new JsonException($"Deserialization failed. Required GeoJson property '{GeoJsonConstants.CoordinatesPropertyName}' not found."); } } }
/// <inheritdoc/> public override object Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { if (reader.TokenType == JsonTokenType.Null) { return(null); } string type = default; double?longitude = default; double?latitude = default; reader.Expect(JsonTokenType.StartObject); while (reader.Read() && reader.TokenType != JsonTokenType.EndObject) { reader.Expect(JsonTokenType.PropertyName); string propertyName = reader.GetString(); reader.Read(); if (string.Equals(TypePropertyName, propertyName, StringComparison.Ordinal)) { reader.Expect(JsonTokenType.String); type = reader.GetString(); } else if (string.Equals(CoordinatesPropertyName, propertyName, StringComparison.Ordinal)) { reader.Expect(JsonTokenType.StartArray); // Longitude reader.Read(); reader.Expect(JsonTokenType.Number); longitude = reader.GetDouble(); // Latitude reader.Read(); reader.Expect(JsonTokenType.Number); latitude = reader.GetDouble(); // Skip the rest. do { reader.Read(); } while (reader.TokenType != JsonTokenType.EndArray); } else { reader.Skip(); } } if (!string.Equals(PointTypeName, type, StringComparison.Ordinal)) { throw new JsonException($"Deserialization of {nameof(GeographyPoint)} failed. Expected geographic type: '{PointTypeName}'."); } if (!longitude.HasValue || !latitude.HasValue) { throw new JsonException($"Deserialization of {nameof(GeographyPoint)} failed. Expected both longitude and latitude."); } return(GeographyPoint.Create(latitude.Value, longitude.Value)); }