Skip to content

SpatialPoint Serialization/Deserialization: Fixes spatial point serialization/deserialization bug#4801

Open
dibahlfi wants to merge 41 commits intomainfrom
users/dikshibahl/SpatialPointSerializationBug
Open

SpatialPoint Serialization/Deserialization: Fixes spatial point serialization/deserialization bug#4801
dibahlfi wants to merge 41 commits intomainfrom
users/dikshibahl/SpatialPointSerializationBug

Conversation

@dibahlfi
Copy link
Copy Markdown
Member

@dibahlfi dibahlfi commented Oct 14, 2024

Pull Request Template

Description

This PR adds System.Text.Json (STJ) serialization/deserialization support for all Microsoft.Azure.Cosmos.Spatial geometry types, resolving a bug where spatial types like Point could not be serialized or deserialized when using UseSystemTextJsonSerializerWithOptions or a custom STJ-based serializer.

closes #4744

Problem

The SDK's spatial types (Point, LineString, Polygon, etc.) only had Newtonsoft.Json converters. When users configured CosmosClientOptions.UseSystemTextJsonSerializerWithOptions, two failures occurred:

  1. Deserialization failureSystem.NotSupportedException on CreateItemAsync / ReadItemAsync because Point lacks a parameterless constructor and had no STJ JsonConverter.
  2. Incorrect serializationPatchItemAsync produced malformed JSON (e.g., raw CLR property names like position.longitude instead of GeoJSON coordinates arrays), because STJ fell back to default property serialization.

Solution

A complete set of 8 new STJ converters mirrors the existing Newtonsoft converter architecture, producing GeoJSON-compliant output identical to the Newtonsoft serializers. Dual [JsonConverter] attributes on each spatial model class allow the runtime to automatically select the correct converter based on the serializer in use.

Design

Architecture

GeometrySTJConverter          ← polymorphic factory for all 7 geometry types
├── PositionSTJConverter      ← [longitude, latitude, altitude?]
├── BoundingBoxSTJConverter   ← [minLon, minLat, ..., maxLon, maxLat, ...]
├── CrsSTJConverter           ← Named / Linked / Unspecified CRS
├── LinearRingSTJConverter    ← closed ring of positions
├── LineStringCoordinatesSTJConverter
├── PolygonCoordinatesSTJConverter
└── STJMetaDataFields         ← centralized GeoJSON property-name constants

Key Design Decisions

Decision Rationale
Parallel implementation (not shared with Newtonsoft) STJ and Newtonsoft have fundamentally different APIs (JsonElement vs JToken); sharing code is not feasible.
GeometrySTJConverter implements full Write() Unlike Newtonsoft's GeometryJsonConverter (CanWrite = false), STJ requires explicit write logic for precise control over property order and formatting.
Dual [JsonConverter] attributes on model classes [Newtonsoft.Json.JsonConverter] and [System.Text.Json.Serialization.JsonConverter] coexist on Geometry, Position, BoundingBox, Crs, etc. The runtime picks the correct one based on which serializer is active.
Numeric formatting matches Newtonsoft Integer coordinates render as x.0; non-integers use "R" format for full precision. This ensures Assert.AreEqual(newtonsoftJson, stjJson) passes in tests.
STJMetaDataFields constants Single source of truth for GeoJSON property names ("type", "coordinates", "geometries", "bbox", "crs", etc.) to prevent string-literal errors.

GeoJSON Types Supported

All 7 GeoJSON geometry types per RFC 7946:

  • Point — single position
  • LineString — array of positions
  • Polygon — array of linear rings (exterior + holes)
  • MultiPoint — array of positions
  • MultiLineString — array of line-string coordinates
  • MultiPolygon — array of polygon coordinates
  • GeometryCollection — array of nested geometry objects (uses "geometries" key)

CRS (Coordinate Reference System) Handling

CRS Type JSON Representation
Default (OGC WGS84) Omitted from output
Named { "type": "name", "properties": { "name": "EPSG:4326" } }
Linked { "type": "link", "properties": { "href": "...", "type": "..." } }
Unspecified null

Type Discrimination

The GeometrySTJConverter uses the "type" field in the GeoJSON payload to determine which Geometry subclass to instantiate during deserialization, mirroring the existing GeometryJsonConverter pattern.

Type of change

  • Bug fix (non-breaking change which fixes an issue)

Changes

New Files (8)

  • Spatial/Converters/STJConverters/GeometrySTJConverter.cs — polymorphic converter handling all geometry types
  • Spatial/Converters/STJConverters/PositionSTJConverter.cs[lon, lat, alt?] array converter
  • Spatial/Converters/STJConverters/BoundingBoxSTJConverter.cs — flat min/max coordinate array
  • Spatial/Converters/STJConverters/CrsSTJConverter.cs — Named, Linked, and Unspecified CRS
  • Spatial/Converters/STJConverters/LinearRingSTJConverter.cs — closed position ring
  • Spatial/Converters/STJConverters/LineStringCoordinatesSTJConverter.cs — multi-line coordinates
  • Spatial/Converters/STJConverters/PolygonCoordinatesSTJConverter.cs — multi-polygon coordinates
  • Spatial/Converters/STJConverters/STJMetaDataFields.cs — GeoJSON property-name constants

Modified Files (17)

  • Spatial model classes (Point.cs, Position.cs, Geometry.cs, BoundingBox.cs, Crs.cs, LineString.cs, Polygon.cs, MultiPoint.cs, MultiLineString.cs, MultiPolygon.cs, GeometryCollection.cs, LinearRing.cs, LineStringCoordinates.cs, PolygonCoordinates.cs) — added [System.Text.Json.Serialization.JsonConverter] attribute
  • CrsJsonConverter.cs — minor cleanup

Test Files (2)

  • STJSpatialTest.cs~20+ test methods covering all geometry types, CRS variants, bounding boxes, additional properties, and round-trip fidelity (verifies STJ output exactly matches Newtonsoft output)
  • ClientTests.cs — emulator integration tests for spatial types with STJ serialization

@dibahlfi dibahlfi added the auto-merge Enables automation to merge PRs label Oct 14, 2024
@dibahlfi dibahlfi self-assigned this Oct 14, 2024
Comment thread Microsoft.Azure.Cosmos/src/Spatial/Point.cs Outdated
Comment thread Microsoft.Azure.Cosmos/src/Spatial/Point.cs Outdated
Comment thread Microsoft.Azure.Cosmos/src/Spatial/Position.cs Outdated
Comment thread Microsoft.Azure.Cosmos/src/Spatial/Position.cs Outdated
Comment thread Microsoft.Azure.Cosmos/src/Spatial/Point.cs
Comment thread Microsoft.Azure.Cosmos/src/Spatial/Position.cs
@dibahlfi dibahlfi added the Do Not Review Marks a PR in "work in progress" state. label Oct 28, 2024
@dibahlfi dibahlfi requested a review from Pilchie as a code owner October 31, 2024 18:35
Comment thread Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Spatial/PointTest.cs Outdated
@sourabh1007
Copy link
Copy Markdown
Contributor

If you are still making the changes, you can convert this PR to "draft" mode.

@xperiandri
Copy link
Copy Markdown

Looks like it needs to be rebased

@xperiandri
Copy link
Copy Markdown

@dibahlfi, any updates?

Comment thread Microsoft.Azure.Cosmos/src/Spatial/GeometryCollection.cs Outdated
Position min = null;
Position max = null;

JsonElement rootElement = JsonDocument.ParseValue(ref reader).RootElement;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's different from existing Newtonsoft conversion. It seems the final form is not 1-1 JSON types conversion (like "prop" = "Value")

All converters need to exactly match existing Newton converts. And also lets please revisit the test coverage also.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let me take a look.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@kirankumarkolli Could you pls take a look again. I matched the STJ conversion to newtonsoft and updated the test cases.

Copy link
Copy Markdown
Contributor

Copilot AI commented Feb 25, 2026

@NaluTripician I've opened a new pull request, #5638, to work on those changes. Once the pull request is ready, I'll request review from you.

Copilot AI and others added 3 commits February 25, 2026 11:51
…e instead of JsonSerializer.Deserialize(GetRawText()) in GeometrySTJConverter (#5638)

All deserialization calls in `GeometrySTJConverter` were going through
`JsonSerializer.Deserialize<T>(element.GetRawText(), options)`, which
unnecessarily re-serializes the already-parsed `JsonElement` back to a
UTF-16 string before deserializing it.

## Changes

- **`GeometrySTJConverter.cs`**: Replaced all 9 occurrences with
`element.Deserialize<T>(options)`, covering:
  - `bbox` → `BoundingBox`
  - `crs` → `Crs`
- `coordinates`/`geometries` for all 7 geometry types (`Point`,
`MultiPoint`, `LineString`, `MultiLineString`, `Polygon`,
`MultiPolygon`, `GeometryCollection`)

**Before:**
```csharp
boundingBox = JsonSerializer.Deserialize<BoundingBox>(bboxElement.GetRawText(), options);
crs = JsonSerializer.Deserialize<Crs>(crsElement.GetRawText(), options);
Position pointCoordinates = JsonSerializer.Deserialize<Position>(rootElement.GetProperty("coordinates").GetRawText(), options);
```

**After:**
```csharp
boundingBox = bboxElement.Deserialize<BoundingBox>(options);
crs = crsElement.Deserialize<Crs>(options);
Position pointCoordinates = rootElement.GetProperty("coordinates").Deserialize<Position>(options);
```

<!-- START COPILOT CODING AGENT TIPS -->
---

💡 You can make Copilot smarter by setting up custom instructions,
customizing its development environment and configuring Model Context
Protocol (MCP) servers. Learn more [Copilot coding agent
tips](https://gh.io/copilot-coding-agent-tips) in the docs.

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: NaluTripician <27316859+NaluTripician@users.noreply.github.com>
NaluTripician and others added 8 commits March 10, 2026 12:12
Addresses review feedback to add PatchItemAsync coverage since the
original issue (#4744) specifically mentions PatchItemAsync having
special handling where spatial converters were ignored.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The STJ test uses JsonNamingPolicy.CamelCase, which serializes
SpatialItem.Location as 'location' (lowercase) in the document.
The PatchOperation path must match the serialized property name,
so '/Location' is corrected to '/location'.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Comment thread Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/ClientTests.cs Outdated
@Maya-Painter
Copy link
Copy Markdown
Contributor

Any reason there are not STJConverters for all of the spatial types?

Maya-Painter
Maya-Painter previously approved these changes Mar 18, 2026
- Replace raw string literals in GeometrySTJConverter.cs with STJMetaDataFields constants
- Replace custom error messages with RMResources.SpatialInvalidGeometryType
- Add STJMetaDataFields.Bbox constant for GeoJSON bbox property
- Revert whitespace-only changes in ClientTests.cs

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@NaluTripician
Copy link
Copy Markdown
Contributor

Any reason there are not STJConverters for all of the spatial types?

All spatial types are covered. The GeometrySTJConverter is a factory converter that handles all 7 geometry types (Point, LineString, Polygon, MultiPoint, MultiLineString, MultiPolygon, GeometryCollection) — mirroring how the Newtonsoft GeometryJsonConverter factory works. Individual STJ converters exist for coordinate/metadata types (Position, LinearRing, LineStringCoordinates, PolygonCoordinates, BoundingBox, CRS), providing exact 1:1 parity with the Newtonsoft converter set.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

auto-merge Enables automation to merge PRs

Projects

Status: Approved

Development

Successfully merging this pull request may close these issues.

Microsoft.Azure.Cosmos.Spatial.Point is not compatible with UseSystemTextJsonSerializerWithOptions or System.Text.Json

10 participants