web-dev-qa-db-ja.com

Newtonsoft.Json.JsonSerializationException( 'System.Data.SqlTypes.SqlDoubleの' Value 'から値を取得中にエラーが発生しました)SqlGeographyをシリアル化しています

データベースSQLサーバー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'を既にインストールしました

問題を示すためにSqlGeography列のあるデータテーブルを使用して、完全なC#プログラム(SQLサーバーのインストールに依存しない)を作成しました 試してみてください

エラーが発生します:

Newtonsoft.Json.JsonSerializationException:「System.Data.SqlTypes.SqlDouble」の「Value」から値を取得中にエラーが発生しました。 --->

System.Data.SqlTypes.SqlNullValueException:データがNullです。このメソッドまたはプロパティは、Null値に対して呼び出すことはできません。 System.Data.SqlTypes.SqlDouble.get_Value()at GetValue(Object)at Newtonsoft.Json.Serialization.DynamicValueProvider.GetValue(Object target)

私は https://github.com/JamesNK/Newtonsoft.Json/issues/99 に達しましたが、それは助けにはなりません。

問題を解決するための助け。

編集:

@dbcコメントに基づいて、jsonの生成に使用される完全なソースコードを提供しました。

完全なエラーメッセージは次のとおりです。

Newtonsoft.Json.JsonSerializationException:「System.Data.SqlTypes.SqlDouble」の「Value」から値を取得中にエラーが発生しました。 --->> System.Data.SqlTypes.SqlNullValueException:データがNullです。このメソッドまたはプロパティは、Null値に対して呼び出すことはできません。 System.Data.SqlTypes.SqlDouble.get_Value()at GetValue(Object)at Newtonsoft.Json.Serialization.DynamicValueProvider.GetValue(Object target)

---内部例外スタックトレースの終了--- at Newtonsoft.Json.Serialization.DynamicValueProvider.GetValue(Object target)

(Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.CalculatePropertyValues(JsonWriter writer、Object value、JsonContainerContractコントラクト、JsonPropertyメンバー、JsonPropertyプロパティ、JsonContract&memberContract、Object&memberValue)at Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.ConrialWriterObject、JsonSerializerInternalWriter.ConrialWriterObject契約、JsonProperty部材、Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.SerializeObjectでNewtonsoft.Json.Serialization.JsonSerializerInternalWriter.SerializeValue(JsonWriterライター、オブジェクト値、JsonContract valueContract、JsonProperty部材、JsonContainerContract containerContractでJsonContainerContract collectionContract、JsonProperty containerProperty)、JsonProperty containerProperty) (JsonWriterライター、オブジェクト値、JsonObjectContractコントラクト、JsonPropertyメンバー、JsonContainerContract collectionContract、JsonProperty containerProperty)at Newtonsoft.Json.Serialization.JsonSerializerI Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.Serialize(JsonWriter jsonWriter、Object value、Type objectType)at ntonWriter.SerializeValue(JsonWriter writer、Object value、JsonContract valueContract、JsonProperty member、JsonProperty containerProperty) .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、JsonWriter writer、オブジェクト値、JsonContractコントラクト、JsonContainerContract collectionContract、JsonProperty containerProperty)

newtonsoft.Json.Serialization.JsonSerializerInternalWriter.SerializeValue(JsonWriter writer、Object value、JsonContract valueContract、JsonProperty member、JsonContainerContract containerContract、JsonProperty containerProperty)at Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.Serializer(Object value、JsonWriter TypesonWriter) Newtonsoft.Json.Json.JsonConvert.SerializeObjectInternal(Object value、Type type、JsonSerializer jsonSerializer)at Newtonsoft.Json.JsonConvert.SerializeObject(Object value、JsonSerializerSettings)のNewtonsoft.Json.JsonSerializer.SerializeInternal(JsonWriter jsonWriter、Object value、Type objectType) F:\ JsonTester.cs:line 104のJsonTester.SerializeToJson()で

Edit2:

@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情報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」の「Value」から値を取得するSystem.Data.SqlTypes.SqlDouble.Errorのシリアル化エラーエラー。

2017-08-24T19:45:32.003 System.Data.DataTableのシリアル化中にエラーが発生しました。 'System.Data.SqlTypes.SqlDouble'の 'Value'から値を取得中にエラーが発生しました。

10
M.Hassan

System.Data.SqlTypesSqlDoubleなどのプリミティブ型は、独自のTypeConverterを実装していないため、そのままではJson.NETでシリアル化できないようです。 docs から:

プリミティブ型

.Net:TypeConverter(文字列に変換可能)
JSON:文字列

これは、これらの型をシリアル化するために custom JsonConverter を実装する必要があります。 Json.NETには、組み込み.Netタイプ用の KeyValuePairConverter など、いくつかの組み込み converters があるため、これは珍しいことではありません。

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フィドルown から分岐しました。

それによって作成されたJSONを逆シリアル化する必要がある場合は、さらに2つの問題があります。まず、 SqlGeographyLat などの Long のプロパティの一部は、取得のみ。この型を完全に逆シリアル化するには、カスタムJsonConverterを作成する必要があります。

第2に、Json.NETには、行の値の複雑なオブジェクトを含むJSONを型なしDataTableに逆シリアル化する機能がありません。したがって、複雑なオブジェクト(シリアル化されたSqlGeographyなど)を含むJSONを逆シリアル化する必要がある場合は、次のオプションがあります。

  1. 型付きDataTableを作成して逆シリアル化します。

  2. here のように、DataTableConverterを直接使用して、既存のDataTableに事前に割り当てられた列を入力します。

  3. 次のような DTOs のリストに逆シリアル化します。

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

    SqlGeographyConverterは、必要に応じて、JsonConverterのカスタムSqlGeographyです。

    そして次に:

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

Jsonコンバーターの実装は、確かにJson.NetでSqlGeographyをシリアル化できないことを処理する1つの方法ですが、はるかに簡単な解決策は、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));
        }
    }
0
Justin