Search code examples
c#jsonjson-deserializationnettopologysuite

How do I deserialize additional properties present in a type for which a converter already exists?


I am attempting to deserialize a geojson FeatureCollection. The API I am querying returns this feature collection with additional properties on the type. The properties are used for cursor-based pagination.

The FeatureCollection is deserialized using 'NetTopologySuite.IO.GeoJsonConverterFactory'. That works great for the first page of results.

The problem is that the NTS (NetTopologySuite) FeatureCollection class does not have these pagination properties and they are thus not deserialized.

The API in question: https://api.pdok.nl/lv/bgt/ogc/v1/api

Some sample containing the "links" property that has the required pagination link.

https://api.pdok.nl/lv/bgt/ogc/v1/collections/pand/items?f=json&limit=1

I've tried deserializing the api response json twice. Once as a feature collection, once as a custom poco containing only the additional properties.

//Link.cs
public class Link
{
    public string Rel { get; set; }
    public string Type { get; set; }
    public string Title { get; set; }
    public string Href { get; set; }
}

//FeatureCollectionResponse.cs
public class FeatureCollectionResponse
{
    public Link[] Links { get; set; }
    public int NumberReturned { get; set; }
}

//Program.cs
using System.Text.Json;
using NetTopologySuite.Features;
using NetTopologySuite.IO.Converters;



var json = """
{
  "type": "FeatureCollection",
  "timeStamp": "2024-05-23T06:59:03Z",
  "links": [
    {
      "rel": "self",
      "title": "This document as GeoJSON",
      "type": "application/geo+json",
      "href": "https://api.pdok.nl/lv/bgt/ogc/v1/collections/pand/items?f=json&limit=1"
    },
    {
      "rel": "alternate",
      "title": "This document as JSON-FG",
      "type": "application/vnd.ogc.fg+json",
      "href": "https://api.pdok.nl/lv/bgt/ogc/v1/collections/pand/items?f=jsonfg&limit=1"
    },
    {
      "rel": "alternate",
      "title": "This document as HTML",
      "type": "text/html",
      "href": "https://api.pdok.nl/lv/bgt/ogc/v1/collections/pand/items?f=html&limit=1"
    },
    {
      "rel": "next",
      "title": "Next page",
      "type": "application/geo+json",
      "href": "https://api.pdok.nl/lv/bgt/ogc/v1/collections/pand/items?cursor=Ag%7CNAynHA&f=json&limit=1"
    }
  ],
  "features": [
    {
      "type": "Feature",
      "geometry": {
        "type": "MultiPolygon",
        "coordinates": [
          [
            [
              [5.42284076748324, 51.3047942505348],
              [5.42284280810938, 51.3047814589294],
              [5.42280900677049, 51.3047793389887],
              [5.42281420113966, 51.3047468161292],
              [5.42283456510663, 51.3047480952593],
              [5.42284800245476, 51.3047489360685],
              [5.42287536474456, 51.3047506534869],
              [5.42286609340301, 51.3047958427393],
              [5.42284076748324, 51.3047942505348]
            ]
          ]
        ]
      },
      "properties": {
        "bag_pnd": "0858100000004395",
        "bronhouder": "G0858",
        "creation_date": "2014/01/14 00:00:00",
        "eind_registratie": null,
        "in_onderzoek": null,
        "in_onderzoek_leeg": "geenWaarde",
        "lokaal_id": "G0858.0000b367299f47c9b4b5235d3dd44937",
        "lv_publicatiedatum": "2014/08/10 16:22:23",
        "plus_status": null,
        "plus_status_codespace": "http://www.geostandaarden.nl/imgeo/def/2.1#VoidReasonValue",
        "plus_status_leeg": "geenWaarde",
        "relatieve_hoogteligging": 0,
        "status": "bestaand",
        "status_codespace": "http://www.geostandaarden.nl/imgeo/def/2.1#Status",
        "status_leeg": null,
        "termination_date": null,
        "termination_date_leeg": "geenWaarde",
        "tijdstip_registratie": "2014/01/20 13:14:55",
        "version": "3fffbd13-0b2d-9c60-87e4-77170c259f2e"
      },
      "id": 1
    }
  ],
  "numberReturned": 1
}
""";

var serializerOptions = new JsonSerializerOptions(JsonSerializerDefaults.Web);
serializerOptions.Converters.Add(new GeoJsonConverterFactory());

var featureCollection = JsonSerializer.Deserialize<FeatureCollection>(json, serializerOptions);
var paginationInformation = JsonSerializer.Deserialize<FeatureCollectionResponse>(json);

if (featureCollection is not null)
{
    Console.WriteLine("featurecollection was deserialized: " + featureCollection[0].Attributes["bag_pnd"]);
}
else{
    Console.WriteLine("feature collection not deserialized");
}

if (paginationInformation?.NumberReturned == 1)
{
    Console.WriteLine("paginationInformation was deserialized");
}
else{
    Console.WriteLine("paginationInformation was not deserialized");
}

The feature collection is properly filled but the properties on the pagination information are all null.

What is the right way extract these additional properties? A wrapper class perhaps. But what would that look like?

Can I create a JsonConverter for the FeatureCollection that handles these additions and delegate the rest of the type to the NTS deserializer?


Solution

  • Turns out my approach of deserializing it twice as two different classes would work as long as I add the serializer options. I skipped that as I though I needed the serializer options only to use the converter. But they are actually needed for case-insensitive property name matching.