将字典序列化为数组(键值对的形式)

37

Json.Net通常会将Dictionary<k,v>序列化为一个集合;

"MyDict": {
  "Apples": {
    "Taste": 1341181398,
    "Title": "Granny Smith",
  },
  "Oranges": {
    "Taste": 9999999999,
    "Title": "Coxes Pippin",
  },
 }

这很不错。从在stackoverflow上的观察来看,这似乎是大多数人想要的。然而,在这种特殊情况下,我想将我的Dictionary<k,v>序列化为数组格式。

"MyDict": [
    "k": "Apples",
    "v": {
        "Taste": 1341181398,
        "Title": "Granny Smith",
    }
  },
    "k:": "Oranges",
    "v:": {
        "Taste": 9999999999,
        "Title": "Coxes Pippin",
    }
  },
]

我现有的字段类型是否有简便的方法执行此操作?例如,我可以添加什么属性来实现吗?


你在期望输出的示例中可能缺少一些括号和大括号。 - Ben Voigt
6个回答

46

另一种实现该目标的方法是使用自定义的ContractResolver,这样您就不必对Dictionary<K,V>进行子类化或每次序列化时应用转换(如其他答案所建议的那样)。

以下解析器将导致所有字典被序列化为一个对象数组,其中包含"Key"和"Value"属性:

class DictionaryAsArrayResolver : DefaultContractResolver
{
    protected override JsonContract CreateContract(Type objectType)
    {
        if (objectType.GetInterfaces().Any(i => i == typeof(IDictionary) || 
           (i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IDictionary<,>))))
        {
            return base.CreateArrayContract(objectType);
        }

        return base.CreateContract(objectType);
    }
}

要使用解析器,将其添加到您的JsonSerializerSettings中,然后像这样将设置传递给JsonConvert.SerializeObject():

JsonSerializerSettings settings = new JsonSerializerSettings();
settings.ContractResolver = new DictionaryAsArrayResolver();

string json = JsonConvert.SerializeObject(obj, settings);

这里有一个可行的演示


正在进行序列化处理,现在要如何反序列化相同的对象? - MSTdev
@MSTdev 同样的想法,使用 JsonConvert.DeserializeObject 并确保将带有解析器的 JsonSerializerSettings 传递给该方法。我更新了演示文稿以进行演示。 - Brian Rogers
@BrianRogers 请查看我的详细问题 https://dev59.com/ZI7ea4cB1Zd3GeqPHuxQ - MSTdev
如果我只想要将Dictionary<K,V>转换怎么办?将两个IDictionary都改为IDictionary<K,V>? - Seaky Lone

29
啊,结果证明这比我想象的简单得多。我的 Dictionary<k,v> 已经被子类化了,我发现我可以用 [JsonArrayAttribute] 对其进行注释。这恰好给了我我需要的格式;
"MyDict": [
  {
    "Key": "Apples",
    "Value": {
        "Taste": 1341181398,
        "Title": "Granny Smith",
    }
  },
  {
    "Key:": "Oranges",
    "Value:": {
        "Taste": 9999999999,
        "Title": "Coxes Pippin",
    }
  },
]

2
有没有一种方法可以通过Json.NET配置将Key和Value属性展开(成为顶层数组对象上的一个属性)? - ryanwebjackson

15

举个例子,我将使用字典:

var myDict = new Dictionary<string,string>() { 
    {"a","123"}, 
    {"b","234"}, 
    {"c","345"} 
};

使用 Newtonsoft.Json.JsonConvert.SerializeObject(myDict) 进行序列化后得到:

{"a":"123","b":"234","c":"345"}
您可以使用LINQ进行转换,创建一个匿名对象,并对其进行序列化:
 var myDictTransformed = from key in myDict.Keys
                         select new { k = key, v = myDict[key] };

或者你可以使用一个真正的对象

class MyDictEntry 
{
    public string k { get; set; }
    public string v { get; set; }
}

而且还可以使用上述或者替代的LINQ语法:

var myDictTransformed = myDict.Keys.AsEnumerable()
                        .Select(key => new MyDictEntry{ 
                            k = key, 
                            v = myDict[key] 
                        });

无论哪种方式,这都会序列化为:

[
  {"k":"a", "v":"123"},
  {"k":"b", "v":"234"},
  {"k":"c", "v":"345"}
]

.NET Fiddle 链接: https://dotnetfiddle.net/LhisVW


12

我找到的最简单的解决方案是将你的Dictionary<string, string>转换为一个List<KeyValuePair<string, string>>。然后,JSON.NET将你的List转换为带有表单{ Key: 'keyname', Value: 'value' }的对象数组。如果你接受所需的模型更改并且不想对你的Dictionary进行子类化,则此方法效果很好。


使用ToList()或ToArray()扩展方法可以轻松实现此操作。 - Mark G

1

gregmac的回答很有帮助,但并不完全适用。以下是相同的想法...没有双关语。

var dictionaryTransformed = dictionary.Select(item => item.Key).Select(i => 
                        new {Key = i, Value = dictionary[i] });

或者当然也可以

var dictionaryTransformed = dictionary.Select(item => 
                        new {item.Key, Value = dictionary[item.Key] });

然后转换为 JSON 格式

var json = (new JavaScriptSerializer()).Serialize( 
                        new { Container = dictionaryTransformed.ToArray() } )

0

我不确定为什么,但是上面列出的Brian Rogers的自定义ContractResolver对我没有起作用。它似乎在内部某个地方陷入了无限循环。可能是由于我的json.net设置中的其他部分引起的。

无论如何 - 这个解决方法对我有用。

public interface IStrongIdentifier
    {
        string StringValue { get; set; }
    }

public class StrongIdentifierKeyedDictionaryWrapper<TKey, TValue> : Dictionary<string, TValue>
        where TKey : IStrongIdentifier
    {
        public void Add(TKey key, TValue value)
        {
            base.Add(key.StringValue, value);
        }

        public void Remove(TKey key)
        {
            base.Remove(key.StringValue);
        }

        public TValue this[TKey index]
        {
            get => base[index.StringValue];
            set => base[index.StringValue] = value;
        }

        public bool ContainsKey(TKey key)
        {
            return base.ContainsKey(key.StringValue);
        }
    }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接