Ради полноты и потому, что вопрос продолжает задаваться (например, здесь ), , пока вы используете Json.NET или DataContractJsonSerializer
(но не JavaScriptSerializer
) , вы можете использовать шаблон адаптера и обернуть NameValueCollection
в IDictionary<string, string[]>
адаптер и сериализовать его, используя любой сериализатор, который полностью поддерживает сериализацию произвольных словарей.
Когда-то такой адаптер выглядит следующим образом:
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<TKey, TValue>
, который также не наследуется от Dictionary<TKey, TValue>
. Подробнее см. Сериализация словарей с помощью JavaScriptSerializer .
При использовании DataContractJsonSerializer
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
поддерживает все следующие функции
- A
null
значение для данного ключа;
- Несколько значений для данного ключа (в этом случае
NameValueCollection.Item[String]
возвращает список значений через запятую);
- Одно значение, содержащее запятую (которую нельзя отличить от случая нескольких значений при использовании
NameValueCollection.Item[String]
).
Таким образом, адаптер должен реализовать IDictionary<string, string[]>
, а не IDictionary<string, string>
, а также позаботиться о обработке массива значений null
.
Пример скрипта (включая некоторые базовые юнит-тесты) здесь: https://dotnetfiddle.net/gVPSi7