web-dev-qa-db-ja.com

Json.netを使用して複合キーで辞書をシリアル化できません

カスタム.netタイプをキーとして持つディクショナリがあります。JSON.netを使用してこのディクショナリをJSONにシリアル化しようとしていますが、シリアル化中にキーを適切な値に変換できません。

class ListBaseClass
{
    public String testA;
    public String testB;
}
-----
var details = new Dictionary<ListBaseClass, string>();
details.Add(new ListBaseClass { testA = "Hello", testB = "World" }, "Normal");
var results = Newtonsoft.Json.JsonConvert.SerializeObject(details);
var data = Newtonsoft.Json.JsonConvert.DeserializeObject<Dictionary<ListBaseClass, string>> results);

これは私に与える-> "{\" JSonSerialization.ListBaseClass\":\" Normal\"}"

しかし、辞書の値としてカスタムタイプがある場合、うまく機能します

  var details = new Dictionary<string, ListBaseClass>();
  details.Add("Normal", new ListBaseClass { testA = "Hello", testB = "World" });
  var results = Newtonsoft.Json.JsonConvert.SerializeObject(details);
  var data = Newtonsoft.Json.JsonConvert.DeserializeObject<Dictionary<string, ListBaseClass>>(results);

これで私に-> "{\" Normal\":{\" testA\":\" Hello\"、\" testB\":\" World\"}}"

Json.netの一部の制限に達した場合、または何か問題がある場合、誰かが提案できますか?

34
Shashwat Gupta

シリアライゼーションガイド の状態(セクション:辞書とハッシュテーブルを参照。リンクについては@Shashwatに感謝):

辞書をシリアル化するとき、辞書のキーは文字列に変換され、JSONオブジェクトのプロパティ名として使用されます。キー用に記述された文字列は、キータイプのToString()をオーバーライドするか、TypeConverterを実装することによってカスタマイズできます。 TypeConverterは、辞書を逆シリアル化するときに、カスタム文字列を再度変換することもサポートします。

Microsoftの「ハウツー」ページで、そのような型コンバーターを実装する方法の便利な例を見つけました。

基本的に、_System.ComponentModel.TypeConverter_を拡張してオーバーライドする必要がありました。

_bool CanConvertFrom(ITypeDescriptorContext context, Type source);

object ConvertFrom(ITypeDescriptorContext context,
                   System.Globalization.CultureInfo culture, object value);

object ConvertTo(ITypeDescriptorContext context, 
                 System.Globalization.CultureInfo culture, 
                 object value, Type destinationType);
_

属性を追加[TypeConverter(typeof(MyClassConverter))]MyClassクラス宣言に追加することも必要でした。

これらを配置することで、辞書のシリアライズとデシリアライズを自動的に行うことができました

26
Gordon Bean

Gordon Beanが提示した答えはおそらく使いたくないでしょう。ソリューションは機能しますが、出力にシ​​リアル化された文字列を提供します。 JSONを使用している場合、文字列表現ではなくオブジェクトのJSON表現が本当に必要なため、理想的な結果が得られません。

たとえば、一意のグリッドポイントを文字列に関連付けるデータ構造があるとします。

class Point
{
    public int x { get; set; }
    public int y { get; set; }
}

public Dictionary<Point,string> Locations { get; set; };

TypeConverterオーバーライドを使用すると、このオブジェクトをシリアル化するときに、このオブジェクトの文字列表現を取得できます。

"Locations": {
  "4,3": "foo",
  "3,4": "bar"
},

しかし、私たちが本当に欲しいのは:

"Locations": {
  { "x": 4, "y": 3 }: "foo",
  { "x": 3, "y": 4 }: "bar"
},

TypeConverterをオーバーライドしてクラスをシリアル化/逆シリアル化するには、いくつかの問題があります。

まず、これはJSONではありません。別の場所でシリアル化および逆シリアル化を処理するために、追加のカスタムロジックを記述する必要がある場合があります。 (たとえば、クライアント層のJavascriptなどでしょうか?)

次に、このオブジェクトを使用する他の場所は、この文字列を生成します。以前は、オブジェクトに正しくシリアル化されていました。

"GridCenterPoint": { "x": 0, "y": 0 },

今シリアル化します:

"GridCenterPoint": "0,0",

TypeConverterのフォーマットを少し制御できますが、オブジェクトではなく文字列としてレンダリングされるという事実から逃れることはできません。

この問題はシリアライザの問題ではありません。json.netはビートを逃さずに複雑なオブジェクトを噛むため、辞書キーの処理方法に問題があります。サンプルオブジェクトを取得して、ListまたはHashsetさえもシリアル化すると、適切なJSONの生成に問題がないことがわかります。これにより、この問題をより簡単に解決できます。

理想的には、json.netに、キーを任意のオブジェクトタイプとしてシリアル化するように通知し、文字列にすることを強制しないようにします。これはオプションではないように思われるので、もう1つの方法は、json.netで機能するものにList<KeyValuePair<T,K>>を与えることです。

KeyValuePairのリストをjson.netのシリアライザーにフィードすると、期待どおりの結果が得られます。たとえば、次のラッパーは、実装することができます。

    private Dictionary<Point, string> _Locations;
    public List<KeyValuePair<Point, string>> SerializedLocations
    {
        get { return _Locations.ToList(); }
        set { _Locations= value.ToDictionary(x => x.Key, x => x.Value); }
    }

Kvpのキーは文字列形式に強制されないため、このトリックは機能します。これはなぜですか?それは私の地獄を打ち負かします。辞書オブジェクトはIEnumerable<KeyValuePair<TKey, TValue>>インターフェイスを実装しているため、kvpsのリストと同じ方法でシリアル化しても問題ありません。誰か(James Newton?)がNewtonsoft辞書シリアライザを作成するときに、複雑なキーを扱うのは面倒であると決定しました。これをもっと厄介な問題にする、考えていないコーナーケースがおそらくあるでしょう。

これは、実際のJSONオブジェクトを生成し、技術的に単純であり、シリアライザーを置き換えることによる副作用を生成しないため、はるかに優れたソリューションです。

11
Roger Hill