6

I'm trying to write a custom JsonConverter for NetTopologySuite geometries.

My model:

public class MyModel
{
    public string myName { get; set; }

    [JsonConverter(typeof(MyPolygonConverter))]
    public Polygon myPolygon { get; set; }
}

My converter:

public class MyPolygonConverter : JsonConverter<Polygon>
{
    public override Polygon ReadJson(JsonReader reader, Type objectType, Polygon existingValue, 
        bool hasExistingValue, JsonSerializer serializer)
    {
        var geoJson = (string)reader.Value;

        // ... not relevant, because reader.Value is null.
    }
}

What I'm doing:

var deSerialized = JsonConvert.DeserializeObject<MyModel>("{\"myName\":\"My Name\",\"myPolygon\":{\"type\":\"Polygon\",\"coordinates\":[[[-100.0,45.0],[-98.0,45.0],[-99.0,46.0],[-100.0,45.0]]]}}");

And what's happening:

My converter is being called, but reader.Value is null.

The examples I've seen online use (string)reader.Value to access the json string that needs to be converted. But in my case reader.Value is null.

How am I supposed to access the JSON string I need to convert?

To be clear - what I have is a model class that contains a property of a class - Polygon - that I convert to GeoJson on serialization that I need to convert back to that class, from the GeoJson.

So, my starting json is:

{
    "myName" : "My Name",
    "myPolygon" : {
        "type" : "Polygon",
        "coordinates" : [
            [
                [-100.0, 45.0],
                [-98.0, 45.0],
                [-99.0, 46.0],
                [-100.0, 45.0]
            ]
        ]
    }
}

And I need to take everything that a child of "myPolygon" and hand it off to my conversion code. In the simple examples I've seen, reader.Value provided the value as a string. In this case it does not, most likely because "myPolygon"'s child isn't a single value, but is instead a complex json object.

I already have code for parsing the value when provided as a single json string. So how can I get the child, as a single json string?

Jeff Dege
  • 11,190
  • 22
  • 96
  • 165
  • If I plug your JSON into https://json2csharp.com/, I get C# code that is very different from the code you have posted. – Robert Harvey Mar 24 '21 at 15:04
  • As you should. I'm not trying to serialize and deserialize the Polygon class into some generic JSON, I need it to serialize into valid GeoJson, and I need to deserialize it back. – Jeff Dege Mar 24 '21 at 15:07
  • Hmm, perhaps a duplicate of [Efficiently get full json string in JsonConverter.ReadJson()](https://stackoverflow.com/q/56944160/3744182), agree? – dbc Mar 24 '21 at 19:21

2 Answers2

11

I think you have a misconception about how JsonConverters work. The reader does not give you the raw JSON. Instead it allows you to step through the tokens in the JSON one by one.

reader.Value is null because your converter is handling an object, not a string, and at the point your converter is called, the reader is positioned on the StartObject token. You need to call reader.Read() in a loop to advance through the object to get the property names and values until you reach the EndObject token.

Here is a fiddle that demonstrates that process, although it doesn't actually populate the Polygon. https://dotnetfiddle.net/MQE6N5

If you really want the JSON string inside the converter, you could load a JObject from the reader, and then call its ToString() method:

public override Polygon ReadJson(JsonReader reader, Type objectType, Polygon existingValue, bool hasExistingValue, JsonSerializer serializer)
{
    JObject jo = JObject.Load(reader);
    string json = jo.ToString(Formatting.None);

    Polygon poly = ConvertJsonToPolygon(json);  // your conversion method
    
    return poly;
}

A better solution IMO is to use the facilities of the JObject to actually do the conversion. You can easily pick values out of the JObject and populate your Polygon class from it:

public override Polygon ReadJson(JsonReader reader, Type objectType, Polygon existingValue, bool hasExistingValue, JsonSerializer serializer)
{
    JObject jo = JObject.Load(reader);
    Polygon poly = new Polygon();
    poly.type = (string)jo["type"];
    poly.coordinates = jo["coordinates"].ToObject<double[][][]>(serializer);
    return poly;
}

Fiddle: https://dotnetfiddle.net/OFSP1h

Brian Rogers
  • 125,747
  • 31
  • 299
  • 300
1

As Brian Rogers pointed out, I was looking at things incorrectly.

But I really didn't want to have to loop through the JsonReader, one token at a time, to build up the json object I need to process.

Fortunately, I don't have to. Json.Net already provides tools to do this:

public override Polygon ReadJson(JsonReader reader, Type objectType, 
    Polygon existingValue, bool hasExistingValue, JsonSerializer serializer)
{
    var jObject = JObject.Load(reader);
    var geoJson = jObject.ToString();

    // Work with the geoJson string...
}

Note - this works when the object is not an array, which it will never be in my use case. If you're doing something where it is, you might need to use JArray instead. If your use case provides that your object might be an array or an object, you might be able to distinguish which you need to use by checking if reader.TokenType == JsonToken.StartObject. I've not explored that, because it's not possible in my use case.

Jeff Dege
  • 11,190
  • 22
  • 96
  • 165