如何将 NameValueCollection 转换为 JSON 字符串?

Posted

技术标签:

【中文标题】如何将 NameValueCollection 转换为 JSON 字符串?【英文标题】:how to convert NameValueCollection to JSON string? 【发布时间】:2011-10-23 15:24:42 【问题描述】:

我试过了:

  NameValueCollection Data = new NameValueCollection();
  Data.Add("foo","baa");
  string json = new javascriptSerializer().Serialize(Data);

它返回:["foo"] 我期待 "foo" : "baa" 我该怎么做?

【问题讨论】:

【参考方案1】:

序列化 NameValueCollection 的一种方法是首先将其转换为 Dictionary,然后序列化 Dictionary。转换成字典:

thenvc.AllKeys.ToDictionary(k => k, k => thenvc[k]);

如果需要频繁转换,也可以创建NameValueCollection的扩展方法:

public static class NVCExtender

    public static IDictionary<string, string> ToDictionary(
                                        this NameValueCollection source)
    
        return source.AllKeys.ToDictionary(k => k, k => source[k]);
    

所以你可以像这样在一行中进行转换:

NameValueCollection Data = new NameValueCollection();
Data.Add("Foo", "baa");

var dict = Data.ToDictionary();

然后就可以序列化字典了:

var json = new JavaScriptSerializer().Serialize(dict);
// you get "Foo":"baa"

但是 NameValueCollection 一个键可以有多个值,例如:

NameValueCollection Data = new NameValueCollection();
Data.Add("Foo", "baa");
Data.Add("Foo", "again?");

如果你序列化这个你会得到"Foo":"baa,again?"

您可以修改转换器以生成IDictionary&lt;string, string[]&gt;

public static IDictionary<string, string[]> ToDictionary(
                                    this NameValueCollection source)

    return source.AllKeys.ToDictionary(k => k, k => source.GetValues(k));

所以你可以得到这样的序列化值:"Foo":["baa","again?"]

【讨论】:

【参考方案2】:

NameValueCollection 不是一个 IDictionary,所以 JavaScriptSerializer 不能像你期望的那样直接序列化它。您需要先将其转换为字典,然后对其进行序列化。

更新:以下关于每个键的多个值的问题,对nvc[key] 的调用将简单地返回它们以逗号分隔,这可能没问题。如果没有,可以随时致电GetValues 并决定适当地处理这些值。更新了下面的代码以显示一种可能的方式。

public class ***_7003740

    static Dictionary<string, object> NvcToDictionary(NameValueCollection nvc, bool handleMultipleValuesPerKey)
    
        var result = new Dictionary<string, object>();
        foreach (string key in nvc.Keys)
        
            if (handleMultipleValuesPerKey)
            
                string[] values = nvc.GetValues(key);
                if (values.Length == 1)
                
                    result.Add(key, values[0]);
                
                else
                
                    result.Add(key, values);
                
            
            else
            
                result.Add(key, nvc[key]);
            
        

        return result;
    

    public static void Test()
    
        NameValueCollection nvc = new NameValueCollection();
        nvc.Add("foo", "bar");
        nvc.Add("multiple", "first");
        nvc.Add("multiple", "second");

        foreach (var handleMultipleValuesPerKey in new bool[]  false, true )
        
            if (handleMultipleValuesPerKey)
            
                Console.WriteLine("Using special handling for multiple values per key");
            
            var dict = NvcToDictionary(nvc, handleMultipleValuesPerKey);
            string json = new JavaScriptSerializer().Serialize(dict);
            Console.WriteLine(json);
            Console.WriteLine();
        
    

【讨论】:

NameValueCollection 每个键可能有多个值,这不能解决该问题。 这种方法可能会导致异常! NameValueCollection 存在(并且.NET 有时不使用字典)的原因是允许每个键有多个值。字典要求每个值只有一个键。 该方法不会抛出异常。如果有多个键,nvc[key] 将返回它们并由 , 字符连接。但为了完整起见,我更新了答案,使其更加明确。 如果查询字符串没有被制作成 NameValueCollection,那么这个方法有效:***.com/questions/12428947/… DotNetFiddle:NameValueCollection 上的多个键dotnetfiddle.net/hnYwGn【参考方案3】:

如果您的字典不打算包含许多条目,则可以使用该类: System.Collections.Specialized.ListDictionary

【讨论】:

【参考方案4】:

为了完整起见,并且由于继续提出问题(例如here),只要您使用 Json.NET 或 DataContractJsonSerializer(但不是 JavaScriptSerializer ,您可以使用adapter pattern 并将NameValueCollection 包装在IDictionary&lt;string, string[]&gt; 适配器中,然后使用任何完全支持序列化任意字典的序列化程序对其进行序列化。

一旦这样的适配器如下:

public class NameValueCollectionDictionaryAdapter<TNameValueCollection> : IDictionary<string, string[]>
    where TNameValueCollection : NameValueCollection, new()

    readonly TNameValueCollection collection;

    public NameValueCollectionDictionaryAdapter() : this(new TNameValueCollection())  

    public NameValueCollectionDictionaryAdapter(TNameValueCollection collection)
    
        this.collection = collection;
    

    // Method instead of a property to guarantee that nobody tries to serialize it.
    public TNameValueCollection GetCollection()  return collection; 

    #region IDictionary<string,string[]> Members

    public void Add(string key, string[] value)
    
        if (collection.GetValues(key) != null)
            throw new ArgumentException("Duplicate key " + key);
        if (value == null)
            collection.Add(key, null);
        else
            foreach (var str in value)
                collection.Add(key, str);
    

    public bool ContainsKey(string key)  return collection.GetValues(key) != null; 

    public ICollection<string> Keys  get  return collection.AllKeys;  

    public bool Remove(string key)
    
        bool found = ContainsKey(key);
        if (found)
            collection.Remove(key);
        return found;
    

    public bool TryGetValue(string key, out string[] value)
    
        return (value = collection.GetValues(key)) != null;
    

    public ICollection<string[]> Values
    
        get
        
            return new ReadOnlyCollectionAdapter<KeyValuePair<string, string[]>, string[]>(this, p => p.Value);
        
    

    public string[] this[string key]
    
        get
        
            var value = collection.GetValues(key);
            if (value == null)
                throw new KeyNotFoundException(key);
            return value;
        
        set
        
            Remove(key);
            Add(key, value);
        
    

    #endregion

    #region ICollection<KeyValuePair<string,string[]>> Members

    public void Add(KeyValuePair<string, string[]> item)  Add(item.Key, item.Value); 

    public void Clear()  collection.Clear(); 

    public bool Contains(KeyValuePair<string, string[]> item)
    
        string[] value;
        if (!TryGetValue(item.Key, out value))
            return false;
        return EqualityComparer<string[]>.Default.Equals(item.Value, value); // Consistent with Dictionary<TKey, TValue>
    

    public void CopyTo(KeyValuePair<string, string[]>[] array, int arrayIndex)
    
        foreach (var item in this)
            array[arrayIndex++] = item;
    

    public int Count  get  return collection.Count;  

    public bool IsReadOnly  get  return false;  

    public bool Remove(KeyValuePair<string, string[]> item)
    
        if (Contains(item))
            return Remove(item.Key);
        return false;
    

    #endregion

    #region IEnumerable<KeyValuePair<string,string[]>> Members

    public IEnumerator<KeyValuePair<string, string[]>> GetEnumerator()
    
        foreach (string key in collection)
            yield return new KeyValuePair<string, string[]>(key, collection.GetValues(key));
    

    #endregion

    #region IEnumerable Members

    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()  return GetEnumerator(); 

    #endregion


public static class NameValueCollectionExtensions

    public static NameValueCollectionDictionaryAdapter<TNameValueCollection> ToDictionaryAdapter<TNameValueCollection>(this TNameValueCollection collection)
        where TNameValueCollection : NameValueCollection, new()
    
        if (collection == null)
            throw new ArgumentNullException();
        return new NameValueCollectionDictionaryAdapter<TNameValueCollection>(collection);
    


public class ReadOnlyCollectionAdapter<TIn, TOut> : CollectionAdapterBase<TIn, TOut, ICollection<TIn>>

    public ReadOnlyCollectionAdapter(ICollection<TIn> collection, Func<TIn, TOut> toOuter)
        : base(() => collection, toOuter)
    
    

    public override void Add(TOut item)  throw new NotImplementedException(); 

    public override void Clear()  throw new NotImplementedException(); 

    public override bool IsReadOnly  get  return true;  

    public override bool Remove(TOut item)  throw new NotImplementedException(); 


public abstract class CollectionAdapterBase<TIn, TOut, TCollection> : ICollection<TOut> 
    where TCollection : ICollection<TIn>

    readonly Func<TCollection> getCollection;
    readonly Func<TIn, TOut> toOuter;

    public CollectionAdapterBase(Func<TCollection> getCollection, Func<TIn, TOut> toOuter)
    
        if (getCollection == null || toOuter == null)
            throw new ArgumentNullException();
        this.getCollection = getCollection;
        this.toOuter = toOuter;
    

    protected TCollection Collection  get  return getCollection();  

    protected TOut ToOuter(TIn inner)  return toOuter(inner); 

    #region ICollection<TOut> Members

    public abstract void Add(TOut item);

    public abstract void Clear();

    public virtual bool Contains(TOut item)
    
        var comparer = EqualityComparer<TOut>.Default;
        foreach (var member in Collection)
            if (comparer.Equals(item, ToOuter(member)))
                return true;
        return false;
    

    public void CopyTo(TOut[] array, int arrayIndex)
    
        foreach (var item in this)
            array[arrayIndex++] = item;
    

    public int Count  get  return Collection.Count;  

    public abstract bool IsReadOnly  get; 

    public abstract bool Remove(TOut item);

    #endregion

    #region IEnumerable<TOut> Members

    public IEnumerator<TOut> GetEnumerator()
    
        foreach (var item in Collection)
            yield return ToOuter(item);
    

    #endregion

    #region IEnumerable Members

    IEnumerator IEnumerable.GetEnumerator()  return GetEnumerator(); 

    #endregion

然后可以简单地为给定的NameValueCollection Data 构造一个改编版:

var adapter = Data.ToDictionaryAdapter();

注意事项:

使用适配器可能比简单地创建一个复制的字典更高效,并且应该与任何完全支持字典序列化的序列化器一起工作。

适配器在将 NameValueCollection 与任何其他需要某种 IDictionary 的代码一起使用时也很有用 - 这是适配器模式的基本优势。

话虽这么说,JavaScriptSerializer 不能与适配器一起使用,因为此序列化程序无法序列化实现 IDictionary&lt;TKey, TValue&gt; 的任意类型,该类型也不继承自 Dictionary&lt;TKey, TValue&gt;。详情请见Serializing dictionaries with JavaScriptSerializer

使用DataContractJsonSerializer 时,可以使用data contract surrogate 机制将NameValueCollection 替换为序列化图中的适配器。

使用 Json.NET 时,NameValueCollection 可以替换为使用 custom JsonConverter 的适配器,如下所示:

public class NameValueJsonConverter<TNameValueCollection> : JsonConverter
    where TNameValueCollection : NameValueCollection, new()

    public override bool CanConvert(Type objectType)
    
        return typeof(TNameValueCollection).IsAssignableFrom(objectType);
    

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    
        if (reader.SkipComments().TokenType == JsonToken.Null)
            return null;

        // Reuse the existing NameValueCollection if present
        var collection = (TNameValueCollection)existingValue ?? new TNameValueCollection();
        var dictionaryWrapper = collection.ToDictionaryAdapter();

        serializer.Populate(reader, dictionaryWrapper);

        return collection;
    

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    
        var collection = (TNameValueCollection)value;
        var dictionaryWrapper = new NameValueCollectionDictionaryAdapter<TNameValueCollection>(collection);
        serializer.Serialize(writer, dictionaryWrapper);
    


public static partial class JsonExtensions

    public static JsonReader SkipComments(this JsonReader reader)
    
        while (reader.TokenType == JsonToken.Comment && reader.Read())
            ;
        return reader;
    

可以使用例如如下:

string json = JsonConvert.SerializeObject(Data, Formatting.Indented, new NameValueJsonConverter<NameValueCollection>());

NameValueCollection支持以下所有

给定键的null 值; 给定键的多个值(在这种情况下,NameValueCollection.Item[String] 返回以逗号分隔的值列表); 包含嵌入逗号的单个值(在使用 NameValueCollection.Item[String] 时无法与多个值的情况区分开来)。

因此,适配器必须实现 IDictionary&lt;string, string[]&gt; 而不是 IDictionary&lt;string, string&gt;,并且还要注意处理 null 值数组。

这里的小提琴示例(包括一些基本的单元测试):https://dotnetfiddle.net/gVPSi7

【讨论】:

以上是关于如何将 NameValueCollection 转换为 JSON 字符串?的主要内容,如果未能解决你的问题,请参考以下文章

csharp 将NameValueCollection转换为Querystring

NameValueCollection 到 URL 查询?

ASP.NET (dotnet 2) IIS7 - NameValueCollection 数组未正确读取

NameValueCollection

.net 配置文件 AppSettings:NameValueCollection 与 KeyValueConfigurationCollection

csharp 对namevaluecollection进行排序