Yesterday, a colleague of mine was having problems implementing a Web API microservice, where client was throwing the exception:
'Cannot create and populate list type System.Collections.Specialized.NameValueCollection. Path ...' on de-serialization of a complex graph containing NameValueCollections.
Web searches returned a number suggestions to replace the NameValueCollection with a dictionary, which did not work in our case, where we needed to use classes as is.
As I've created the initial reference architecture, I felt obliged to help resolve the issue. As said, Web searches for a solution didn't return anything closely applicable, I've taken a bit deeper look at the Json.NET library documentation, and shortly afterwards decided to create a custom converter.
It turned out to be easy. Here's the source code:
To user the converter, add it to the converters collection as follows:
'Cannot create and populate list type System.Collections.Specialized.NameValueCollection. Path ...' on de-serialization of a complex graph containing NameValueCollections.
Web searches returned a number suggestions to replace the NameValueCollection with a dictionary, which did not work in our case, where we needed to use classes as is.
As I've created the initial reference architecture, I felt obliged to help resolve the issue. As said, Web searches for a solution didn't return anything closely applicable, I've taken a bit deeper look at the Json.NET library documentation, and shortly afterwards decided to create a custom converter.
It turned out to be easy. Here's the source code:
/// <summary>
/// Custom converter for (de)serializing NameValueCollection
/// Add an instance to the settings Converters collection
/// </summary>
public class NameValueCollectionConverter : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var collection = value as NameValueCollection;
if (collection == null)
return;
writer.WriteStartObject();
foreach (var key in collection.AllKeys)
{
writer.WritePropertyName(key);
writer.WriteValue(collection.Get(key));
}
writer.WriteEndObject();
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var nameValueCollection = new NameValueCollection();
var key = "";
while (reader.Read())
{
if (reader.TokenType == JsonToken.StartObject)
{
nameValueCollection = new NameValueCollection();
}
if (reader.TokenType == JsonToken.EndObject)
{
return nameValueCollection;
}
if (reader.TokenType == JsonToken.PropertyName)
{
key = reader.Value.ToString();
}
if (reader.TokenType == JsonToken.String)
{
nameValueCollection.Add(key, reader.Value.ToString());
}
}
return nameValueCollection;
}
public override bool CanConvert(Type objectType)
{
return objectType == typeof(NameValueCollection);
}
}
To user the converter, add it to the converters collection as follows:
var settings = new JsonSerializerSettings()
{
ContractResolver = new CamelCasePropertyNamesContractResolver(),
TypeNameHandling = TypeNameHandling.All,
Formatting = Formatting.Indented
};
settings.Converters.Add(new NameValueCollectionConverter());
JsonConvert.DefaultSettings = () => settings;
Nice job done in implementing and documenting Json.NET!
This solution didn't work for me. I only get a list of the keys, and no values.
ReplyDeleteLooks as if the custom converter is not called. As far as I can remember, this worked in ASP.NET MVC. If you are using ASP.NET Web API, try configuring custom converter as follows:
Deletepublic static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Web API configuration and services
// Web API routes
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
var jsonFormatter = config.Formatters.JsonFormatter;
var settings = jsonFormatter.SerializerSettings;
settings.Converters.Add(new NameValueCollectionConverter());
}
}
ran into an error when trying to deserialize objects with null name value collections.
ReplyDeleteNeeded a check before the while(reader.read())
if (reader.TokenType == JsonToken.Null)
return null;
May want to consider preserving multiple values; swapping out the writer loop:
ReplyDelete```
foreach (var key in collection.AllKeys) {
writer.WritePropertyName(key);
var maybeManyValues = collection.GetValues(key);
// let the settings decide how to write null value
if (maybeManyValues == null)
writer.WriteValue(maybeManyValues);
// write as a single value
else if (maybeManyValues.Length == 1)
writer.WriteValue(maybeManyValues[0]);
else {
writer.WriteStartArray();
foreach (var val in maybeManyValues) {
writer.WriteValue(val);
}
writer.WriteEndArray();
}
}
```