I'm building a SPA using Angular,Breeze and Web API 2 following the approach as outlined by John Papa in his latest PluralSight course.
Everything works well and I can pull information, update, insert, delete back to the server. However I'm using Spatial Types, and when I try to update an entity with a spatial type I get the following error
An exception of type 'Newtonsoft.Json.JsonSerializationException' occurred in Newtonsoft.Json.dll but was not handled in user code
Additional information: Error getting value from 'WellKnownValue' on 'System.Data.Entity.Spatial.DbGeometry'.
The inner exception seems to point to the fact that the WellKnownValue is null, its not though, as I have checked the JSON being sent to the server which is then sent to the Breeze ContextProvider and saved using the SaveChanges method.
{
"entities": [
{
"TableKey": 2,
"CaseName": "Mikhail Lermontov",
"StartDate": "2013-06-11T00:00:00Z",
"EndDate": null,
"IsCurrent": true,
"SRID": 109,
"Shape": {
"$id": "2",
"$type": "System.Data.Entity.Spatial.DbGeometry, EntityFramework",
"Geometry": {
"$id": "3",
"$type": "System.Data.Entity.Spatial.DbGeometryWellKnownValue, EntityFramework",
"CoordinateSystemId": 2193,
"WellKnownText": "POLYGON ((1695943 5462665, 1713098 5462665, 1713098 5449659, 1695943 5449659, 1695943 5462665))"
}
},
"SpillLocation": "Marlborough Sounds",
"Image": "http://www.nzmaritime.co.nz/images/lm5.jpg\r\n",
"DefaultBaseMapKey": 2,
"__unmapped": {
"isPartial": false
},
"entityAspect": {
"entityTypeName": "DatSpillCase:#Osiris.Model",
"defaultResourceName": "DatSpillCases",
"entityState": "Modified",
"originalValuesMap": {
"CaseName": "Mikhail Lermontov"
},
"autoGeneratedKey": {
"propertyName": "TableKey",
"autoGeneratedKeyType": "Identity"
}
}
}
],
"saveOptions": {}
}
So my question is, is possible to deserialize DbGeometry types within the NewtonSoft library, and if not, what suggestions are there to get around that.
A common way to deserialize JSON is to first create a class with properties and fields that represent one or more of the JSON properties. Then, to deserialize from a string or a file, call the JsonSerializer. Deserialize method.
JSON is a format that encodes objects in a string. Serialization means to convert an object into that string, and deserialization is its inverse operation (convert string -> object).
The process whereby a lower-level format (e.g. that has been transferred over a network, or stored in a data store) is translated into a readable object or other data structure. In JavaScript, for example, you can deserialize a JSON string to an object by calling the function JSON.
System.Data.Spatial.DbGeometry
does not play nicely with Newtonsoft.Json
You need to create a JsonConverter
to convert the DbGeometry
public class DbGeometryConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType.IsAssignableFrom(typeof(string));
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JObject location = JObject.Load(reader);
JToken token = location["Geometry"]["WellKnownText"];
string value = token.ToString();
DbGeometry converted = DbGeometry.PolygonFromText(value, 2193);
return converted;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
// Base serialization is fine
serializer.Serialize(writer, value);
}
}
Then on your property in your model add the attribute
[JsonConverter(typeof(DbGeometryConverter))]
public DbGeometry Shape { get; set; }
Now when you hit your BreezeController the deserialization will be handled by our new DbGeometryConverter.
Hope it helps.
The answer above works great, but is hardcoded for SRID (CoordinateSystemId) 2193. The Coordinate System Id can however be present in the serialised data as shown in the question, or it can be present in the WellKnownText "SRID=2193;POINT (0 0)". Also this method will only read a polygon, but the WellKnownText can be a lot of things, like Geometry Collections, Point, Linestring, etc. To retreive this the ReadJson method can be updated to use the more generic FromText method as shown below. Here is the class above updated with a more generic Coordinate System, and also for any Geometry Type. I have also added the Geography version for reference.
public class DbGeometryConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType.IsAssignableFrom(typeof(string));
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JObject location = JObject.Load(reader);
JToken token = location["Geometry"]["WellKnownText"];
string value = token.ToString();
JToken sridToken = location["Geometry"]["CoordinateSystemId"];
int srid;
if (sridToken == null || int.TryParse(sridToken.ToString(), out srid) == false || value.Contains("SRID"))
{
//Set default coordinate system here.
srid = 0;
}
DbGeometry converted;
if (srid > 0)
converted = DbGeometry.FromText(value, srid);
else
converted = DbGeometry.FromText(value);
return converted;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
// Base serialization is fine
serializer.Serialize(writer, value);
}
}
public class DbGeographyConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType.IsAssignableFrom(typeof(string));
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JObject location = JObject.Load(reader);
JToken token = location["Geography"]["WellKnownText"];
string value = token.ToString();
JToken sridToken = location["Geography"]["CoordinateSystemId"];
int srid;
if (sridToken == null || int.TryParse(sridToken.ToString(), out srid) == false || value.Contains("SRID"))
{
//Set default coordinate system here.
//NOTE: Geography should always have an SRID, and it has to match the data in the database else all comparisons will return NULL!
srid = 0;
}
DbGeography converted;
if (srid > 0)
converted = DbGeography.FromText(value, srid);
else
converted = DbGeography.FromText(value);
return converted;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
// Base serialization is fine
serializer.Serialize(writer, value);
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With