NetTopologySuite / NetTopologySuite.IO.GeoJSON

GeoJSON IO module for NTS.
BSD 3-Clause "New" or "Revised" License
111 stars 46 forks source link

Incorrect GeoJson deserialization in ASP.NET Core #34

Closed YuriiNskyi closed 5 years ago

YuriiNskyi commented 5 years ago

I'm currently stucked with this issue and absolutely can't figure out how to properly work with GeoJsonSerializer in ASP.NET Core.

Here is my simple extension, which enables GeoJson serialization:

        public static MvcJsonOptions AddGeoJsonConverters(this MvcJsonOptions options)
        {
            options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
            var converters = GeoJsonSerializer.Create(
                new JsonSerializerSettings { ReferenceLoopHandling = ReferenceLoopHandling.Ignore },
                new GeometryFactory(new PrecisionModel(), 4326)).Converters;

            foreach (var converter in converters)
            {
                options.SerializerSettings.Converters.Add(converter);
            }

            return options;
        }

It perfectly works on serialization, unfortunately deserialization throws the exception:

Unable to find a constructor to use for type NetTopologySuite.Geometries.LineString. A class should either have a default constructor, one constructor with arguments or a constructor marked with the JsonConstructor attribute.

What else I supposed to set up to properly deserialize GeoJson?

FObermaier commented 5 years ago

Could you please provide the full stack trace, the geojson you want to deserialize and possibly a unit test?

YuriiNskyi commented 5 years ago

It's really hard to provide unit test right now, here's stack trace which might help:

at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateNewObject(JsonReader reader, JsonObjectContract objectContract, JsonProperty containerMember, JsonProperty containerProperty, String id, Boolean& createdFromNonDefaultCreator) in //Src/Newtonsoft.Json/Serialization/JsonSerializerInternalReader.cs:line 2290 at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateObject(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerMember, Object existingValue) in //Src/Newtonsoft.Json/Serialization/JsonSerializerInternalReader.cs:line 476 at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.SetPropertyValue(JsonProperty property, JsonConverter propertyConverter, JsonContainerContract containerContract, JsonProperty containerProperty, JsonReader reader, Object target) in //Src/Newtonsoft.Json/Serialization/JsonSerializerInternalReader.cs:line 1032 at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.PopulateObject(Object newObject, JsonReader reader, JsonObjectContract contract, JsonProperty member, String id) in //Src/Newtonsoft.Json/Serialization/JsonSerializerInternalReader.cs:line 2386 at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateObject(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerMember, Object existingValue) in //Src/Newtonsoft.Json/Serialization/JsonSerializerInternalReader.cs:line 485 at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.PopulateList(IList list, JsonReader reader, JsonArrayContract contract, JsonProperty containerProperty, String id) in //Src/Newtonsoft.Json/Serialization/JsonSerializerInternalReader.cs:line 1663 at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateList(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, Object existingValue, String id) in //Src/Newtonsoft.Json/Serialization/JsonSerializerInternalReader.cs:line 875 at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.SetPropertyValue(JsonProperty property, JsonConverter propertyConverter, JsonContainerContract containerContract, JsonProperty containerProperty, JsonReader reader, Object target) in //Src/Newtonsoft.Json/Serialization/JsonSerializerInternalReader.cs:line 1032 at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.PopulateObject(Object newObject, JsonReader reader, JsonObjectContract contract, JsonProperty member, String id) in //Src/Newtonsoft.Json/Serialization/JsonSerializerInternalReader.cs:line 2386 at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateObject(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerMember, Object existingValue) in //Src/Newtonsoft.Json/Serialization/JsonSerializerInternalReader.cs:line 485 at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.Deserialize(JsonReader reader, Type objectType, Boolean checkAdditionalContent) in //Src/Newtonsoft.Json/Serialization/JsonSerializerInternalReader.cs:line 167 at Newtonsoft.Json.JsonSerializer.DeserializeInternal(JsonReader reader, Type objectType) in //Src/Newtonsoft.Json/JsonSerializer.cs:line 907 at Newtonsoft.Json.JsonConvert.DeserializeObject(String value, Type type, JsonSerializerSettings settings) in //Src/Newtonsoft.Json/JsonConvert.cs:line 828 at Newtonsoft.Json.JsonConvert.DeserializeObject[T](String value, JsonSerializerSettings settings) in //Src/Newtonsoft.Json/JsonConvert.cs:line 786 at MyProjectControllers.MyController.GetSimpleRoute() in D:..MyController.cs:line 117

Json is pretty straightforward:

{  
    "type":"LineString",
    "coordinates":[  
      [  
        10,
        20
      ],
      [  
        20,
        10
      ]
    ]
  }
airbreather commented 5 years ago

Hmm... this works on its own, so I'm not sure what's going on. I have very little experience with ASP.NET Core, so I don't know what else to check for:

const string Text = @"
{  
    ""type"":""LineString"",
    ""coordinates"":[  
      [  
        10,
        20
      ],
      [  
        20,
        10
      ]
    ]
  }
  ";

var settings = new JsonSerializerSettings { ReferenceLoopHandling = ReferenceLoopHandling.Ignore };
foreach (var conv in GeoJsonSerializer.Create(settings, new GeometryFactory(new PrecisionModel(), 4326)).Converters)
{
    settings.Converters.Add(conv);
}

Console.WriteLine(JsonConvert.DeserializeObject<LineString>(Text, settings));
airbreather commented 5 years ago

What happens if you pass in options.SerializerSettings to GeoJsonSerializer.Create instead of a new instance that you create on-the-fly? Probably irrelevant, but I'm really grasping here...

YuriiNskyi commented 5 years ago

@airbreather Well, this is really good catch, but unfortunately problem is still here. Your example perfectly works, now it needs to be working on ASP.NET Core. Seems that something is wrong with JSON settings setup in ASP.NET Core.

FObermaier commented 5 years ago

The stack trace reveals that there is no NetTopologySuite.IO.GeoJson object involved which makes me think that the converters for the GeoJsonSerializer are not picked up.

YuriiNskyi commented 5 years ago

@FObermaier That's the silly reason of mistake. I was using simple static JsonConvert.DeserializeObject, inside my controller, without any settings passed in, because I thought that ASP.NET Core must provide them. But that works only for arguments and return values of controller methods.

Thanks for the help, it's allright with this package, only my confidence was a mistake.

FObermaier commented 5 years ago

@YuriiNskyi, glad you got it fixed. If there is any information missing on the HowTo ... page, please add it. Thanks.