Deserializing DbGeometry с Newtonsoft.Json
Я создаю SPA, используя Angular, Breeze и Web API 2, следуя подходу, описанному Джоном Папой в его последнем курсе PluralSight.
Все работает хорошо, и я могу извлекать информацию, обновлять, вставлять, удалять обратно на сервер. Однако я использую пространственные типы, и когда я пытаюсь обновить объект с пространственным типом, я получаю следующую ошибку:
Исключение типа "Newtonsoft.Json.JsonSerializationException" произошло в Newtonsoft.Json.dll, но не был обработан в коде пользователя
Дополнительная информация: Ошибка получения значения из "WellKnownValue" на 'System.Data.Entity.Spatial.DbGeometry'.
Внутреннее исключение, похоже, указывает на то, что значение WellKnownValue равно null, но, хотя я проверял отправку JSON на сервер, который затем отправляется на Breeze ContextProvider и сохраняется с помощью метода SaveChanges.
{
"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": {}
}
Итак, мой вопрос: можно ли десериализовать типы DbGeometry в библиотеке NewtonSoft, а если нет, то какие предложения существуют, чтобы обойти это.
Ответы
Ответ 1
System.Data.Spatial.DbGeometry
не играет хорошо с Newtonsoft.Json
Вам нужно создать JsonConverter
для преобразования 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);
}
}
Затем на вашем свойстве в вашей модели добавьте атрибут
[JsonConverter(typeof(DbGeometryConverter))]
public DbGeometry Shape { get; set; }
Теперь, когда вы нажмете на BreezeController, десериализация будет обработана нашим новым DbGeometryConverter.
Надеюсь, что это поможет.
Ответ 2
Ответ выше отлично работает, но жестко запрограммирован для SRID (CoordinateSystemId) 2193.
Однако идентификатор системы координат может присутствовать в сериализованных данных, как показано в вопросе, или он может присутствовать в WellKnownText "SRID = 2193; POINT (0 0)". Также этот метод будет читать только многоугольник, но для WellKnownText может быть много чего, например, коллекции геометрии, точки, Linestring и т.д. Чтобы восстановить этот метод, метод ReadJson можно обновить, чтобы использовать более общий метод FromText, как показано ниже.
Здесь приведен класс выше, обновленный с более общей системой координат, а также для любого типа геометрии. Я также добавил версию географии для справки.
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["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.
//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);
}
}
Ответ 3
Я не понимаю, почему нет. На линии с (DbGeometryWellKnownValue):
"$type": "System.Data.Entity.Spatial.DbGeometryWellKnownValue, EntityFramework",
должно ли это быть (DbGeometry.WellKnownValue)?
"$type": "System.Data.Entity.Spatial.DbGeometry.WellKnownValue, EntityFramework",