web-dev-qa-db-ja.com

Newtonsoft.JSONはTypeConverter属性を持つモデルを変換できません

C#MVCアプリケーションがあります。このアプリケーションはデータをJSON文字列としてXMLドキュメントとMySQLDBテーブルに格納します。

最近、JSON文字列をMySQLデータベースフィールドに保存する、変換するNewtonsoft.Json経由のC#オブジェクトという要件を受け取ったので、TypeConverterを実装してJSONを変換することにしました文字列をカスタムC#モデルに変換します。

残念ながら、TypeConverter属性がC#モデルに追加されているの場合、ソリューションのどこでも次のコマンドを使用してJSON文字列を逆シリアル化することはできません。

JsonConvert.DeserializeObject<Foo>(json);

属性を削除すると問題は解決しますが、これによりMySQL DBフィールドをカスタムC#オブジェクトに変換できなくなります。

これが私のC#モデルで、TypeConverter属性が追加されています。

using System.ComponentModel;

[TypeConverter(typeof(FooConverter))]
public class Foo
{
    public bool a { get; set; }
    public bool b { get; set; }
    public bool c { get; set; }
    public Foo(){}
}

これが私のTypeConverter Class

using Newtonsoft.Json;
using System;
using System.ComponentModel;

    public class FooConverter : TypeConverter
    {
        public override bool CanConvertFrom(ITypeDescriptorContext context, System.Type sourceType)
        {
            if (sourceType == typeof(string))
            {
                return true;
            }
            return base.CanConvertFrom(context, sourceType);
        }
        public override object ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value)
        {
            if (value is string)
            {
                string s = value.ToString().Replace("\\","");
                Foo f = JsonConvert.DeserializeObject<Foo>(s);
                return f;
            }
            return base.ConvertFrom(context, culture, value);
        }
    }
}

Fooクラスに属性を追加するとすぐに、次のエラーが発生します:

現在のJSONオブジェクト(例:{"name": "value"})をタイプ 'Models.Foo'にデシリアライズできません。これは、タイプが正しくデシリアライズするためにJSON文字列値を必要とするためです。

このエラーを修正するには、JSONをJSON文字列値に変更するか、逆シリアル化された型を変更して、通常の.NET型(たとえば、整数のようなプリミティブ型ではなく、のようなコレクション型ではない)にします。 JSONオブジェクトから逆シリアル化できる配列またはリスト)。 JsonObjectAttributeを型に追加して、JSONオブジェクトから強制的に逆シリアル化することもできます。

次の文字列を使用しています(TypeConverter属性を追加しなくても完全に機能します):

"{\"Foo\":{\"a\":true,\"b\":false,\"c\":false}}"

ここで何が起こっているのかわからない、何かアイデアはありますか?

どうもありがとう!!!

更新

プロパティとしてFooを使用したテストクラスを受け入れるMVC APIコントローラーまたはFooをプロパティとして受け入れるコントローラー)でのアクションにも問題があることを発見しました。オブジェクトTypeConverter属性がFooクラスに追加されたとき。

問題のあるテストコントローラーの例を次に示します。

public class TestController : ApiController
{
    [AcceptVerbs("POST", "GET")]
    public void PostTestClass(TestClass t)
    {
        // Returns null when TypeConverter attribute is added to the Foo Class
        return t.Foo; 
    }
    AcceptVerbs("POST", "GET")]
    public void PostFooObj(Foo f)
    {
        // Returns null when TypeConverter attribute is added to the Foo Class
        return f;
    }
}

TypeConverterは、WebAPIモデルのバインディングをオーバーライドする問題を引き起こしている可能性があり、上記のいずれかのアクションが次の構造でAJAXを介してJSONを受信すると、nullを返します。

// eg. PostTestClass(TestClass T)
{'Foo': {'a': false,'b': true,'c': false}};

// eg. PostFooObj(Foo f)
{'a': false,'b': true,'c': false}

TypeConverter属性がFooクラスに追加されると、ルートが見つかるとすぐにFooConverterTypeConverterクラスの次のメソッドが呼び出されます。

    public override bool CanConvertFrom(ITypeDescriptorContext context, System.Type sourceType)
    {
        if (sourceType == typeof(string))
        {
            return true;
        }
        return base.CanConvertFrom(context, sourceType);
    }

FooConverter TypeControllerのConvertFromメソッドは、ApiControllerのアクションによって呼び出されないため、問題の原因である可能性があります。

これも同様の状況であり、TypeConverter属性がなくてもコントローラーのアクションは正常に機能します。

それ以上の助けは大歓迎です!

どうもありがとう。

13
Steve

ここで起こっていることがいくつかあります。まず、予備的な問題:TypeConverterが適用されていない場合でも、JSONはクラスFooに対応せず、Fooプロパティを含むコンテナクラスに対応します。インスタンス:

public class TestClass
{
    public Foo Foo { get; set; }
}

つまりJSON文字列を指定すると、以下は機能しません。

var json = "{\"Foo\":{\"a\":true,\"b\":false,\"c\":false}}";
var foo = JsonConvert.DeserializeObject<Foo>(json);

しかし、次のようになります。

var test = JsonConvert.DeserializeObject<TestClass>(json);

これは単に質問の間違いだと思うので、プロパティFooを含むクラスを逆シリアル化しようとしていると仮定します。

表示されている主な問題は、Json.NETTypeConverterを使用して、シリアル化されるクラスをJSON文字列に変換しようとすることです。 docs から:

プリミティブ型

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

ただし、JSONでは、FooはJSON文字列ではなく、JSONobjectであるため、型コンバーターが適用されると逆シリアル化は失敗します。埋め込まれた文字列は次のようになります。

{"Foo":"{\"a\":true,\"b\":false,\"c\":false}"}

すべての引用符がどのようにエスケープされているかに注意してください。また、これに一致するようにFooオブジェクトのJSON形式を変更した場合でも、TypeConverterとJson.NETが相互に再帰的に呼び出そうとするため、逆シリアル化は失敗します。

したがって、Json.NETによるTypeConverterの使用をグローバルに無効にし、他のすべての状況でTypeConverterの使用を維持しながら、デフォルトのシリアル化にフォールバックする必要があります。 Json.NET属性 タイプコンバーターの使用を無効にするために適用できるため、これは少し注意が必要です。代わりに、特別なコントラクトリゾルバーと特別なJsonConverterが必要です。それ:

public class NoTypeConverterJsonConverter<T> : JsonConverter
{
    static readonly IContractResolver resolver = new NoTypeConverterContractResolver();

    class NoTypeConverterContractResolver : DefaultContractResolver
    {
        protected override JsonContract CreateContract(Type objectType)
        {
            if (typeof(T).IsAssignableFrom(objectType))
            {
                var contract = this.CreateObjectContract(objectType);
                contract.Converter = null; // Also null out the converter to prevent infinite recursion.
                return contract;
            }
            return base.CreateContract(objectType);
        }
    }

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

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        return JsonSerializer.CreateDefault(new JsonSerializerSettings { ContractResolver = resolver }).Deserialize(reader, objectType);
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        JsonSerializer.CreateDefault(new JsonSerializerSettings { ContractResolver = resolver }).Serialize(writer, value);
    }
}

そしてそれを次のように使用します:

[TypeConverter(typeof(FooConverter))]
[JsonConverter(typeof(NoTypeConverterJsonConverter<Foo>))]
public class Foo
{
    public bool a { get; set; }
    public bool b { get; set; }
    public bool c { get; set; }
    public Foo() { }
}

public class FooConverter : TypeConverter
{
    public override bool CanConvertFrom(ITypeDescriptorContext context, System.Type sourceType)
    {
        if (sourceType == typeof(string))
        {
            return true;
        }
        return base.CanConvertFrom(context, sourceType);
    }
    public override object ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value)
    {
        if (value is string)
        {
            string s = value.ToString();
            //s = s.Replace("\\", "");
            Foo f = JsonConvert.DeserializeObject<Foo>(s);
            return f;
        }
        return base.ConvertFrom(context, culture, value);
    }
}

フィドル

最後に、おそらく型コンバーターにConvertToメソッドも実装する必要があります。 方法:型コンバーターを実装する を参照してください。

26
dbc

クラスではなく構造体がある場合でも、Nullable<Foo>を(逆)シリアル化しようとすると、受け入れられた回答は無限再帰になります。

CreateContractを次のように変更しないようにするには:

        protected override JsonContract CreateContract(Type objectType)
        {
            if (typeof(T).IsAssignableFrom(objectType)
                || Nullable.GetUnderlyingType(objectType) != null && typeof(T).IsAssignableFrom(Nullable.GetUnderlyingType(objectType)))
            {
                var contract = this.CreateObjectContract(objectType);
                contract.Converter = null; // Also null out the converter to prevent infinite recursion.
                return contract;
            }
            return base.CreateContract(objectType);
        }
0
PhillipM

この動作を回避する簡単な方法は、変換チェックからORを削除する、つまり|| destinationType == typeof(string)を削除することです。

以下の例。

    public class DepartmentBindModelConverter : TypeConverter
    {
        public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
        {
            return destinationType == typeof(DepartmentViewModel); // Removed || destinationType == typeof(string), to allow newtonsoft json convert model with typeconverter attribute
        }

        public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
        {
            if (value == null)
                return null;

            if (destinationType == typeof(DepartmentViewModel) && value is DepartmentBindModel)
            {
                var department = (DepartmentBindModel) value;

                return new DepartmentViewModel
                {
                    Id = department.Id,
                    Name = department.Name,
                    GroupName = department.GroupName,
                    ReturnUrl = department.ReturnUrl
                };

            }

            return base.ConvertTo(context, culture, value, destinationType);
        }
    }
}
0
alhpe