【问题标题】:Newtonsoft.Json.JsonSerializationException (Error getting value from 'Value' on 'System.Data.SqlTypes.SqlDouble) serializing SqlGeographyNewtonsoft.Json.JsonSerializationException(从'System.Data.SqlTypes.SqlDouble'上的'Value'获取值时出错)序列化SqlGeography
【发布时间】:2018-02-01 13:33:33
【问题描述】:

我尝试在数据库 SQL Server 2012 中使用 Newtonsoft.Json 版本“Newtonsoft.Json.10.0.3”将 DataTable 对象序列化为 Json。

该表有一个类型为“geography”的列,其中包含SqlGeography 类型的实例。

用于生成json的代码:

    public string SerializeToJson()
    {

     var connstring1 ="Data Source=server1;Initial Catalog=database1;user=xxx;password=yyy";
        var sql = "SELECT  * FROM table_1 "; //table_1 has a column of type geography
        using (var c1 = new SqlConnection(connstring1))
        {
            c1.Open();
            var da = new SqlDataAdapter()
            {
                SelectCommand = new SqlCommand(sql, c1)
            };

            DataSet ds1 = new DataSet("table");
            da.Fill(ds1, "table");
            var dt = ds1.Tables[0];

            //serialize to Json

            try
            {
                var options = new JsonSerializerSettings
                {
                    Formatting = Formatting.None
                };
                //this line fire exception for geography type
                var json = JsonConvert.SerializeObject(dt, options);
                return json;
            }
            catch (Exception ex)
            {

                Console.WriteLine(ex);
            }                
        }
    }

我已经从 sql 2012 的功能包中安装了程序集“Microsoft.SqlServer.Types”

我创建了一个完整的 C# 程序(独立于 sql server 安装),使用带有 SqlGeography 列的数据表来显示问题 Try it

我得到错误:

Newtonsoft.Json.JsonSerializationException:从“System.Data.SqlTypes.SqlDouble”上的“Value”获取值时出错。 --->

System.Data.SqlTypes.SqlNullValueException:数据为空。 不能对 Null 值调用此方法或属性。 在 System.Data.SqlTypes.SqlDouble.get_Value() 在 GetValue(对象) 在 Newtonsoft.Json.Serialization.DynamicValueProvider.GetValue(Object 目标)

我联系了https://github.com/JamesNK/Newtonsoft.Json/issues/993,但无济于事。

任何解决问题的帮助。

编辑:

基于@dbc cmets,我提供了用于生成json的完整源代码。

完整的错误信息是:

Newtonsoft.Json.JsonSerializationException:从“System.Data.SqlTypes.SqlDouble”上的“Value”获取值时出错。 ---> >System.Data.SqlTypes.SqlNullValueException:数据为空。不能对 Null 值调用此方法或属性。 在 System.Data.SqlTypes.SqlDouble.get_Value() 在 GetValue(对象) 在 Newtonsoft.Json.Serialization.DynamicValueProvider.GetValue(Object 目标)

--- 内部异常堆栈跟踪结束 --- 在 Newtonsoft.Json.Serialization.DynamicValueProvider.GetValue(Object 目标)

在 Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.CalculatePropertyValues(JsonWriter writer, Object value, JsonContainerContract contract, JsonProperty member, JsonProperty property, JsonContract& memberContract, Object& memberValue) 在 Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.SerializeObject (JsonWriter writer, Object value, JsonObjectContract contract, JsonProperty member, JsonContainerContract collectionContract, JsonProperty containerProperty) 在 Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.SerializeValue( JsonWriter writer,Object 值,JsonContract valueContract,JsonProperty 成员 , JsonContainerContract containerContract, JsonProperty containerProperty) 在 Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.SerializeObject (JsonWriter writer, Object value, JsonObjectContract contract, JsonProperty member, JsonContainerContract collectionContract, JsonProperty containerProperty) 在 Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.SerializeValue( JsonWriter writer,Object 值,JsonContract valueContract,JsonProperty 成员 , JsonContainerContract containerContract, JsonProperty containerProperty) 在 Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.Serialize(JsonWriter jsonWriter, Object value, Type objectType) 在 Newtonsoft.Json.Serialization.JsonSerializerProxy.SerializeInternal(JsonWriter jsonWriter,对象值,类型 rootType) 在 Newtonsoft.Json.Converters.DataTableConverter.WriteJson(JsonWriter 作家, 对象值,JsonSerializer 序列化器) 在 Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.SerializeConver table(JsonWriter writer, JsonConverter转换器, Object value, JsonContract contract, JsonContainerContract collectionContract, JsonProperty containerProperty)

在 Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.SerializeValue( JsonWriter writer,Object 值,JsonContract valueContract,JsonProperty 成员 , JsonContainerContract containerContract, JsonProperty containerProperty) 在 Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.Serialize(JsonWriter jsonWriter, Object value, Type objectType) 在 Newtonsoft.Json.JsonSerializer.SerializeInternal(JsonWriter jsonWriter,对象值,类型 objectType) 在 Newtonsoft.Json.JsonConvert.SerializeObjectInternal(对象值,类型类型,JsonSerializer jsonSerializer) 在 Newtonsoft.Json.JsonConvert.SerializeObject(对象值,JsonSerializerSettings 设置) 在 F:\JsonTester.cs:line 104 中的 JsonTester.SerializeToJson() 处

编辑2:

我按照@dbc 的描述启用了跟踪,并获得以下日志:

2017-08-24T19:45:31.941 Info 开始使用转换器 Newtonsoft.Json.Converters.DataTableConverter 序列化 System.Data.DataTable。路径''。

2017-08-24T19:45:31.972 信息开始序列化 Microsoft.SqlServer.Types.SqlGeography。路径“[0].f1”。

2017-08-24T19:45:32.003 Info 开始序列化 System.Data.SqlTypes.SqlInt32.Path '[0].f1.STSrid'。

2017-08-24T19:45:32.003 信息已完成序列化 System.Data.SqlTypes.SqlInt32。路径“[0].f1.STSrid”。

2017-08-24T19:45:32.003 信息开始序列化 System.Data.SqlTypes.SqlDouble。路径'[0].f1.Lat'。

2017-08-24T19:45:32.003 信息已完成序列化 System.Data.SqlTypes.SqlDouble。路径'[0].f1.Lat'。

2017-08-24T19:45:32.003 信息开始序列化 System.Data.SqlTypes.SqlDouble。路径“[0].f1.Long”。

2017-08-24T19:45:32.003 信息已完成序列化 System.Data.SqlTypes.SqlDouble。路径“[0].f1.Long”。

2017-08-24T19:45:32.003 信息开始序列化 System.Data.SqlTypes.SqlDouble。路径“[0].f1.Z”。

2017-08-24T19:45:32.003 序列化 System.Data.SqlTypes.SqlDouble 时出错。从“System.Data.SqlTypes.SqlDouble”上的“Value”获取值时出错。

2017-08-24T19:45:32.003 错误序列化 System.Data.DataTable 时出错。从“System.Data.SqlTypes.SqlDouble”上的“Value”获取值时出错。

【问题讨论】:

  • 1) 有什么方法可以提供minimal reproducible example? 2) 我没有安装 SQL 服务器,所以我无法对此进行测试,但是如果您使用来自JSON.net serialize directly from oledbconnectionDataReaderConvertercommand.ExecuteReader() 返回的IDataReader 直接序列化会发生什么?
  • 而且,如果您不能提供minimal reproducible example,您能否至少在edit 中包含异常的完整ToString() 输出,包括异常类型、消息、回溯和内部异常?
  • 谢谢@dbc。我用完整的代码和错误消息更新了我的问题。 IDatareader 示例很棒,但只提供序列化,我需要反序列化 json。你能给我提供一个 IDataReader 的反序列化示例吗:)。
  • 1) 你能像here 那样启用跟踪并让我们知道日志显示的内容吗?看起来 Json.NET 无法序列化 SqlGeometry 类型的实例,但我想确认一下。 2) 要从SqlCommand 获取数据读取器,请参阅Retrieving Data Using a DataReader
  • @dbc,我使用带有 SqlGeography 列的数据表创建了一个完整的 C# 程序(独立于 sql server 安装)来显示问题。可以在线试用dotnetfiddle.net/8MMQOW

标签: c# sql-server json geolocation json.net


【解决方案1】:

看起来像System.Data.SqlTypes 中的SqlDouble 等原始类型无法通过Json.NET 开箱即用地序列化,因为它们没有实现自己的TypeConverter。来自docs

原始类型

.Net:TypeConverter(可转换为字符串)
JSON:字符串

这将有必要实现custom JsonConverter 来序列化这些类型。 Json.NET 有几个内置的converters,例如用于内置.Net 类型的KeyValuePairConverter,所以这并不罕见。

SqlBooleanSqlBinarySqlDouble 等不共享除 INullable 之外的公共基类或接口这一事实需要一些看起来重复的代码:

public static class SqlPrimitiveConverters
{
    public static JsonSerializerSettings AddSqlConverters(this JsonSerializerSettings settings)
    {
        foreach (var converter in converters)
            settings.Converters.Add(converter);
        return settings;
    }

    static readonly JsonConverter[] converters = new JsonConverter[]
    {
        new SqlBinaryConverter(),
        new SqlBooleanConverter(),
        new SqlByteConverter(),
        new SqlDateTimeConverter(),
        new SqlDecimalConverter(),
        new SqlDoubleConverter(),
        new SqlGuidConverter(),
        new SqlInt16Converter(),
        new SqlInt32Converter(),
        new SqlInt64Converter(),
        new SqlMoneyConverter(),
        new SqlSingleConverter(),
        new SqlStringConverter(),
        // TODO: converters for primitives from System.Data.SqlTypes that are classes not structs:
        // SqlBytes, SqlChars, SqlXml
        // Maybe SqlFileStream
    };
}

abstract class SqlPrimitiveConverterBase<T> : JsonConverter where T : struct, INullable, IComparable
{
    protected abstract object GetValue(T sqlValue);

    public override bool CanConvert(Type objectType)
    {
        return typeof(T) == objectType;
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        T sqlValue = (T)value;
        if (sqlValue.IsNull)
            writer.WriteNull();
        else
        {
            serializer.Serialize(writer, GetValue(sqlValue));
        }
    }
}

class SqlBinaryConverter : SqlPrimitiveConverterBase<SqlBinary>
{
    protected override object GetValue(SqlBinary sqlValue) { return sqlValue.Value; }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        if (reader.TokenType == JsonToken.Null)
            return SqlBinary.Null;
        return (SqlBinary)serializer.Deserialize<byte[]>(reader);
    }
}

class SqlBooleanConverter : SqlPrimitiveConverterBase<SqlBoolean>
{
    protected override object GetValue(SqlBoolean sqlValue) { return sqlValue.Value; }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        if (reader.TokenType == JsonToken.Null)
            return SqlBoolean.Null;
        return (SqlBoolean)serializer.Deserialize<bool>(reader);
    }
}

class SqlByteConverter : SqlPrimitiveConverterBase<SqlByte>
{
    protected override object GetValue(SqlByte sqlValue) { return sqlValue.Value; }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        if (reader.TokenType == JsonToken.Null)
            return SqlByte.Null;
        return (SqlByte)serializer.Deserialize<byte>(reader);
    }
}

class SqlDateTimeConverter : SqlPrimitiveConverterBase<SqlDateTime>
{
    protected override object GetValue(SqlDateTime sqlValue) { return sqlValue.Value; }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        if (reader.TokenType == JsonToken.Null)
            return SqlDateTime.Null;
        return (SqlDateTime)serializer.Deserialize<DateTime>(reader);
    }
}

class SqlDecimalConverter : SqlPrimitiveConverterBase<SqlDecimal>
{
    protected override object GetValue(SqlDecimal sqlValue) { return sqlValue.Value; }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        if (reader.TokenType == JsonToken.Null)
            return SqlDecimal.Null;
        return (SqlDecimal)serializer.Deserialize<decimal>(reader);
    }
}

class SqlDoubleConverter : SqlPrimitiveConverterBase<SqlDouble>
{
    protected override object GetValue(SqlDouble sqlValue) { return sqlValue.Value; }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        if (reader.TokenType == JsonToken.Null)
            return SqlDouble.Null;
        return (SqlDouble)serializer.Deserialize<double>(reader);
    }
}

class SqlGuidConverter : SqlPrimitiveConverterBase<SqlGuid>
{
    protected override object GetValue(SqlGuid sqlValue) { return sqlValue.Value; }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        if (reader.TokenType == JsonToken.Null)
            return SqlGuid.Null;
        return (SqlGuid)serializer.Deserialize<Guid>(reader);
    }
}

class SqlInt16Converter : SqlPrimitiveConverterBase<SqlInt16>
{
    protected override object GetValue(SqlInt16 sqlValue) { return sqlValue.Value; }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        if (reader.TokenType == JsonToken.Null)
            return SqlInt16.Null;
        return (SqlInt16)serializer.Deserialize<short>(reader);
    }
}

class SqlInt32Converter : SqlPrimitiveConverterBase<SqlInt32>
{
    protected override object GetValue(SqlInt32 sqlValue) { return sqlValue.Value; }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        if (reader.TokenType == JsonToken.Null)
            return SqlInt32.Null;
        return (SqlInt32)serializer.Deserialize<int>(reader);
    }
}

class SqlInt64Converter : SqlPrimitiveConverterBase<SqlInt64>
{
    protected override object GetValue(SqlInt64 sqlValue) { return sqlValue.Value; }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        if (reader.TokenType == JsonToken.Null)
            return SqlInt64.Null;
        return (SqlInt64)serializer.Deserialize<long>(reader);
    }
}

class SqlMoneyConverter : SqlPrimitiveConverterBase<SqlMoney>
{
    protected override object GetValue(SqlMoney sqlValue) { return sqlValue.Value; }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        if (reader.TokenType == JsonToken.Null)
            return SqlMoney.Null;
        return (SqlMoney)serializer.Deserialize<decimal>(reader);
    }
}

class SqlSingleConverter : SqlPrimitiveConverterBase<SqlSingle>
{
    protected override object GetValue(SqlSingle sqlValue) { return sqlValue.Value; }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        if (reader.TokenType == JsonToken.Null)
            return SqlSingle.Null;
        return (SqlSingle)serializer.Deserialize<float>(reader);
    }
}

class SqlStringConverter : SqlPrimitiveConverterBase<SqlString>
{
    protected override object GetValue(SqlString sqlValue) { return sqlValue.Value; }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        if (reader.TokenType == JsonToken.Null)
            return SqlString.Null;
        return (SqlString)serializer.Deserialize<string>(reader);
    }
}

工作 .Net fiddle 从您的 own 分叉。

如果您需要反序列化由此创建的 JSON,您还有两个额外的问题。首先,SqlGeography 的一些属性,例如LatLong 是只获取的。您将需要创建一个自定义 JsonConverter 来完全反序列化此类型。

其次,Json.NET 无法将带有复杂对象的 JSON 反序列化为无类型的 DataTable。因此,如果您需要反序列化包含复杂对象的 JSON(例如您的序列化 SqlGeography),您有以下选择:

  1. 创建并反序列化为类型化的DataTable

  2. 直接使用DataTableConverter 使用预先分配的列填充预先存在的DataTable,如here 所示。

  3. 反序列化为DTOs 的列表,如下所示:

    public class TableRowDTO
    {
        [JsonConverter(typeof(SqlGeographyConverter))]
        public SqlGeography f1 { get; set; }
        public int id { get; set; }
    }
    

    根据需要,SqlGeographyConverterSqlGeography 的自定义 JsonConverter

    然后做:

    var settings = new JsonSerializerSettings().AddSqlConverters();
    var list = JsonConvert.DeserializeObject<List<TableRowDTO>>(jsonString, settings);
    

【讨论】:

  • 智能解决方案。我同意你的担忧。使用转换器对数据表进行序列化是可以的。似乎需要修改使用 List 的反序列化,因为它会生成 GEOMETRYCOLLECTION (应该是 SqlGeography )。
  • 如果有人遇到这个问题,我会在 GitHub 上回复 revived 这个问题,JNK 现在可能会查看它。
  • 是的,在 @Fwd079 之后 - 我自己也遇到了这个问题。
【解决方案2】:

实现 Json 转换器无疑是处理无法在 Json.Net 中序列化 SqlGeography 的一种方法,但是我认为更简单的解决方案是不直接序列化 SqlGeography。相反,创建一个包含来自 SqlGeography 的所有数据的自定义类,然后对其进行序列化/反序列化。

public class SqlGeographyResult
    {
        private byte[] bytes { get; }

        public SqlGeographyResult(SqlGeography geography)
        {
            bytes = geography.Serialize().Value;
        }

        public SqlGeography ToGeography()
        {
            return SqlGeography.Deserialize(new System.Data.SqlTypes.SqlBytes(bytes));
        }
    }

【讨论】:

    猜你喜欢
    • 2021-05-24
    • 1970-01-01
    • 2020-03-15
    • 2022-10-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多