2017-08-23 18 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