首页 文章

Newtonsoft.Json.JsonSerializationException(错误从'System.Data.SqlTypes.SqlDouble上的'Value'获取值)序列化SqlGeography

提问于
浏览
7

我尝试在数据库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值上调用此方法或属性 . 在Newtonsoft.Json.Serialization.DynamicValueProvider.GetValue(对象目标)的GetValue(Object)处的System.Data.SqlTypes.SqlDouble.get_Value()处

我达到了https://github.com/JamesNK/Newtonsoft.Json/issues/993,但它无能为力 .

任何帮助解决问题 .

Edit:

基于@dbc注释,我提供了用于生成json的完整源代码 .

完整的错误消息是:

Newtonsoft.Json.JsonSerializationException:从'System.Data.SqlTypes.SqlDouble'上的'Value'获取值时出错 . --- >> System.Data.SqlTypes.SqlNullValueException:数据为空 . 无法在Null值上调用此方法或属性 . 在Newtonoft.Json.Serialization.DynamicValueProvider.GetValue(对象目标)的GetValue(Object)处的System.Data.SqlTypes.SqlDouble.get_Value()---内部异常堆栈跟踪的结束---在Newtonsoft.Json.Serialization处 . Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.SerializeObject(Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.CalculatePropertyValues(JsonWriter writer,Object value,JsonContainerContract contract,JsonProperty member,JsonProperty property,JsonContract&memberContract,Object&memberValue)中的DynamicValueProvider.GetValue(Object target)在Newtonsoft的Newtonoft.Json.Serialization.JsonSerializerInternalWriter.SerializeValue(JsonWriter writer,Object value,JsonContract valueContract,JsonProperty成员,JsonContainerContract containerContract,JsonProperty containerProperty)中的JsonWriter编写器,Object值,JsonObjectContract Contract ,JsonProperty成员,JsonContainerContract collectionContract,JsonProperty containerProperty) . JSON .Serialization.JerialSerializerInternalWriter.SerializeObject(JsonWriter writer,Object value,JsonObjectContract contract,JsonProperty member,JsonContainerContract collectionContract,JsonProperty containerProperty)at Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.SerializeValue(JsonWriter writer,Object value,JsonContract valueContract,JsonProperty member,JsonContainerContract containerContract ,JunesProperty containerProperty)Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.Serialize(JsonWriter jsonWriter,Object value,Type objectType)at Newtonsoft.Json.Serialization.JsonSerializerProxy.SerializeInternal(JsonWriter jsonWriter,Object value,Type rootType)at Newtonsoft.Json.Converters NewPathoft.Json.Serialization.JsonSerializerInternalWriter.SerializeConver表中的.DataTableConverter.WriteJson(JsonWriter writer,Object value,JsonSerializer serializer)(JsonWriter writer,JsonConverter转换器,Object值,JsonContract Contract ,JsonContainerCo) ntract collectionContract,JsonProperty containerProperty)at Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.SerializeValue(JsonWriter writer,Object value,JsonContract valueContract,JsonProperty member,JsonContainerContract containerContract,JsonProperty containerProperty)at Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.Serialize(JsonWriter jsonWriter,Object在Newtonsoft.Json.Json.Ivson.Serialize对象中的Newtonsoft.Json.JsonSerializer.SerializeInternal(JsonWriter jsonWriter,Object value,Type objectType)的Newtonsoft.Json.JsonConvert.SerializeObjectInternal(Object value,Type type,JsonSerializer jsonSerializer)中的值,类型objectType)(对象值,JsonSerializerSettings设置)在F:\ JsonTester.cs中的JsonTester.SerializeToJson():第104行

Edit2:

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

2017-08-24T19:45:31.941 Info已开始使用转换器序列化System.Data.DataTableNewtonsoft.Json.Converters.DataTableConverter . 路径'' . 2017-08-24T19:45:31.972 Info已开始序列化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 Info完成序列化System.Data.SqlTypes.SqlInt32 . 路径'[0] .f1.STSrid' . 2017-08-24T19:45:32.003 Info已开始序列化System.Data.SqlTypes.SqlDouble . 路径'[0] .f1.Lat' . 2017-08-24T19:45:32.003 Info完成序列化System.Data.SqlTypes.SqlDouble . 路径'[0] .f1.Lat' . 2017-08-24T19:45:32.003 Info已开始序列化System.Data.SqlTypes.SqlDouble . 路径'[0] .f1.Long' . 2017-08-24T19:45:32.003 Info完成序列化System.Data.SqlTypes.SqlDouble . 路径'[0] .f1.Long' . 2017-08-24T19:45:32.003 Info已开始序列化System.Data.SqlTypes.SqlDouble . 路径'[0] .f1.Z' . 2017-08-24T19:45:32.003错误序列化System.Data.SqlTypes.SqlDouble.Error从'System.Data.SqlTypes.SqlDouble'上的'Value'获取值时出错 . 2017-08-24T19:45:32.003错误序列化System.Data.DataTable时出错 . 从'System.Data.SqlTypes.SqlDouble'上的'Value'获取值时出错 .

1 回答

  • 12

    看起来像 System.Data.SqlTypesSqlDouble 这样的原始类型无法通过Json.NET进行开箱即用,因为它们没有实现自己的 TypeConverter . 来自docs

    原语类型.Net:TypeConverter(可转换为String)JSON:String

    这有必要实现custom JsonConverter来序列化这些类型 . 对于内置的.Net类型,Json.NET有几个内置的converters,例如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)是get-only . 您需要创建自定义 JsonConverter 才能完全反序列化此类型 .

    其次,Json.NET无法将具有行值的复杂对象的JSON反序列化为无类型的 DataTable . 因此,如果需要反序列化包含复杂对象的JSON(例如序列化的 SqlGeography ),则可以使用以下选项:

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

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

    • 反序列化为DTOs列表,如下所示:

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

    其中 SqlGeographyConverter 根据需要为 SqlGeography 自定义 JsonConverter .

    然后做:

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

相关问题