2017-08-23 21 views
3

我尝试使用Newtonsoft.Json版本'Newtonsoft.Json.10.0.3'将一个DataTable对象序列化为Json '在数据库SQL服务器2012中。Newtonsoft.Json.JsonSerializationException(从'Value'获取值'System.Data.SqlTypes.SqlDouble'时出错)序列化SqlGeography

该表具有类型为'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); 
      }     
     } 
    } 

我已经安装从2012

我已经创建了一个完整的C#程序SQL的功能包的程序集 'Microsoft.SqlServer.Types'(独立的

:使用带有SqlGeography列的DataTable,借以说明问题 Try it

我得到错误的SQL Server安装)

Newtonsoft.Json.JsonSerializationException: Error getting value from 'Value' on 'System.Data.SqlTypes.SqlDouble'. --->

System.Data.SqlTypes.SqlNullValueException: Data is Null. This method or property cannot be called on Null values. at System.Data.SqlTypes.SqlDouble.get_Value() at GetValue(Object) at Newtonsoft.Json.Serialization.DynamicValueProvider.GetValue(Object target)

我达到了https://github.com/JamesNK/Newtonsoft.Json/issues/993,但它没有帮助。

任何帮助来解决问题。

编辑:

基于@dbc意见,我提供用于生成JSON完整的源代码。

完整的错误信息是:

Newtonsoft.Json.JsonSerializationException: Error getting value from 'Value' on 'System.Data.SqlTypes.SqlDouble'. ---> >System.Data.SqlTypes.SqlNullValueException: Data is Null. This method or property cannot be called on Null values. at System.Data.SqlTypes.SqlDouble.get_Value() at GetValue(Object) at Newtonsoft.Json.Serialization.DynamicValueProvider.GetValue(Object target)

--- End of inner exception stack trace --- at Newtonsoft.Json.Serialization.DynamicValueProvider.GetValue(Object target)

at Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.CalculatePropertyValues(JsonWriter writer, Object value, JsonContainerContract contract, JsonProperty member, JsonProperty property, JsonContract& memberContract, Object& memberValue) at Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.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, JsonProperty containerProperty) at Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.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, JsonProperty containerProperty) at 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.DataTableConverter.WriteJson(JsonWriter writer, Object value, JsonSerializer serializer) at Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.SerializeConver table(JsonWriter writer, JsonConverter converter, Object value, JsonContract contract, JsonContainerContract 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 value, Type objectType) at Newtonsoft.Json.JsonSerializer.SerializeInternal(JsonWriter jsonWriter, Object value, Type objectType) at Newtonsoft.Json.JsonConvert.SerializeObjectInternal(Object value, Type type, JsonSerializer jsonSerializer) at Newtonsoft.Json.JsonConvert.SerializeObject(Object value, JsonSerializerSettings settings) at JsonTester.SerializeToJson() in F:\JsonTester.cs:line 104

EDIT2:

启用由@dbc描述的追踪,并获得以下日志:

2017-08-24T19:45:31.941 Info Started serializing System.Data.DataTable with converter Newtonsoft.Json.Converters.DataTableConverter. Path ''.

2017-08-24T19:45:31.972 Info Started serializing Microsoft.SqlServer.Types.SqlGeography. Path '[0].f1'.

2017-08-24T19:45:32.003 Info Started serializing System.Data.SqlTypes.SqlInt32.Path '[0].f1.STSrid'.

2017-08-24T19:45:32.003 Info Finished serializing System.Data.SqlTypes.SqlInt32. Path '[0].f1.STSrid'.

2017-08-24T19:45:32.003 Info Started serializing System.Data.SqlTypes.SqlDouble. Path '[0].f1.Lat'.

2017-08-24T19:45:32.003 Info Finished serializing System.Data.SqlTypes.SqlDouble. Path '[0].f1.Lat'.

2017-08-24T19:45:32.003 Info Started serializing System.Data.SqlTypes.SqlDouble. Path '[0].f1.Long'.

2017-08-24T19:45:32.003 Info Finished serializing System.Data.SqlTypes.SqlDouble. Path '[0].f1.Long'.

2017-08-24T19:45:32.003 Info Started serializing System.Data.SqlTypes.SqlDouble. Path '[0].f1.Z'.

2017-08-24T19:45:32.003 Error Error serializing System.Data.SqlTypes.SqlDouble.Error getting value from 'Value' on 'System.Data.SqlTypes.SqlDouble'.

2017-08-24T19:45:32.003 Error Error serializing System.Data.DataTable. Error getting value from 'Value' on 'System.Data.SqlTypes.SqlDouble'.

+0

1)是否有可以提供一个[MCVE]什么办法? 2)我没有安装SQL服务器,所以我不能测试这个,但是如果你直接从'command.ExecuteReader()'返回的'IDataReader'使用[JSON.net的直接序列化中的'DataReaderConverter'来自oledbconnection](https://stackoverflow.com/a/33837306/3744182)? – dbc

+0

而且,如果你不能[mcve],你是否至少可以将你的问题包含在包含异常类型,消息,追溯和内部异常的异常的完整的'ToString()'输出中? – dbc

+0

谢谢@dbc。我用完整的代码和错误消息更新了我的问题。 IDatareader的例子很好,但只提供序列化,我需要deseriialize json。你能否提供一个IDataReader的反序列化例子:)。 –

回答

5

看样子尽管原始类型如中的SqlDouble不能由Json.NET直接序列化,因为他们不实施他们自己的TypeConverter。从docs

Primitive Types

.Net: TypeConverter (convertible to String)
JSON: String

这将有必要实施custom JsonConverter序列化这些类型。 Json.NET有几种内置的.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 fiddleown分叉。

如果您需要反序列化由此创建的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; } 
    } 
    

    其中SqlGeographyConverter是,根据需要,自定义JsonConverterSqlGeography

    然后执行:

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

智能解决方案。我同意你的担忧。数据表使用你的转换器的序列化是好的。似乎使用列表的反序列化需要修改,因为它生成GEOMETRYCOLLECTION(它应该是SqlGeography)。 –

+1

如果有人遇到这个问题,我已经[恢复](https://github.com/JamesNK/Newtonsoft.Json/issues/993)GitHub和JNK上的问题现在可能会看到它。 – Fwd079

+0

是的,接着@ Fwd079 - 我自己刚刚坠入了这个问题。 – Rynkadink