how to convert NameValueCollection to JSON string?
One way to serialize NameValueCollection is by first converting it to Dictionary and then serialize the Dictionary. To convert to dictionary:
thenvc.AllKeys.ToDictionary(k => k, k => thenvc[k]);
If you need to do the conversion frequently, you can also create an extension method to NameValueCollection:
public static class NVCExtender
{
public static IDictionary<string, string> ToDictionary(
this NameValueCollection source)
{
return source.AllKeys.ToDictionary(k => k, k => source[k]);
}
}
so you can do the conversion in one line like this:
NameValueCollection Data = new NameValueCollection();
Data.Add("Foo", "baa");
var dict = Data.ToDictionary();
Then you can serialize the dictionary:
var json = new JavaScriptSerializer().Serialize(dict);
// you get {"Foo":"baa"}
But NameValueCollection can have multiple values for one key, for example:
NameValueCollection Data = new NameValueCollection();
Data.Add("Foo", "baa");
Data.Add("Foo", "again?");
If you serialize this you will get {"Foo":"baa,again?"}
.
You can modify the converter to produce IDictionary<string, string[]>
instead:
public static IDictionary<string, string[]> ToDictionary(
this NameValueCollection source)
{
return source.AllKeys.ToDictionary(k => k, k => source.GetValues(k));
}
So you can get serialized value like this: {"Foo":["baa","again?"]}
.
NameValueCollection
isn't an IDictionary, so the JavaScriptSerializer
cannot serialize it as you expect directly. You'll need to first convert it into a dictionary, then serialize it.
Update: following questions regarding multiple values per key, the call to nvc[key]
will simply return them separated by a comma, which may be ok. If not, one can always call GetValues
and decide what to do with the values appropriately. Updated the code below to show one possible way.
public class StackOverflow_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();
}
}
}
If your dictionary is not intended to contain many entries, you can use the class:
System.Collections.Specialized.ListDictionary
For completeness' sake, and because the question continues to get asked (e.g. here), as long as you are using Json.NET or DataContractJsonSerializer
(but not JavaScriptSerializer
), you could use the adapter pattern and wrap the NameValueCollection
in an IDictionary<string, string[]>
adapter, and serialize that using any serializer that fully supports serializing arbitrary dictionaries.
Once such adapter is as follows:
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
}
Then an adapted can be constructed for a given NameValueCollection Data
simply by doing:
var adapter = Data.ToDictionaryAdapter();
Notes:
-
Using the adapter may be be more performant than simply creating a copied dictionary, and should work well with any serializer that fully supports dictionary serialization.
The adapter might also be useful in using a
NameValueCollection
with any other code that expects anIDictionary
of some sort - this is the fundamental advantage of the adapter pattern. That being said,
JavaScriptSerializer
cannot be used with the adapter because this serializer cannot serialize an arbitrary type implementingIDictionary<TKey, TValue>
that does not also inherit fromDictionary<TKey, TValue>
. For details see Serializing dictionaries with JavaScriptSerializer.When using
DataContractJsonSerializer
, aNameValueCollection
can be replaced with an adapter in the serialization graph by using the data contract surrogate mechanism.-
When using Json.NET a
NameValueCollection
can be replaced with an adapter using a customJsonConverter
such as the following: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; } }
Which could be used e.g. as follows:
string json = JsonConvert.SerializeObject(Data, Formatting.Indented, new NameValueJsonConverter<NameValueCollection>());
-
NameValueCollection
supports all of the following- A
null
value for a given key; - Multiple values for a given key (in which case
NameValueCollection.Item[String]
returns a comma-separated list of values); - A single value containing an embedded comma (which cannot be distinguished from the case of multiple values when using
NameValueCollection.Item[String]
).
Thus the adapter must implementIDictionary<string, string[]>
rather thanIDictionary<string, string>
and also take care to handle anull
value array. - A
Sample fiddle (including some basic unit testing) here: https://dotnetfiddle.net/gVPSi7