How to ignore empty arrays using JsonConvert.DeserializeObject? - json.net

I am reading in a list of objects from JSON using this call:
Rootobject userInfo = JsonConvert.DeserializeObject<Rootobject>(File.ReadAllText(strFileName));
But I get an exception Cannot deserialize the current JSON array. If one of the arrays within one of the class objects is empty. As long as there is data everything works.
Here is an example of JSON that is tripping up the Deserializer:
This is normal type of data for the Venue object:
"venue": {
"venue_id": 696895,
"venue_name": "Blackfinn Ameripub",
"venue_slug": "blackfinn-ameripub",
"primary_category": "Food",
"parent_category_id": "4d4b7105d754a06374d81259",
"categories": {
"count": 1,
"items": [
{
"category_name": "American Restaurant",
"category_id": "4bf58dd8d48988d14e941735",
"is_primary": true
}
]
},
"is_verified": false
},
And here is what is causing the exception, an empty array:
"venue": [
],
I have tried using the JsonSerializerSettings options including DefaultValueHandling, NullValueHandling and MissingMemberHandling but none of them seem to prevent the error.
Any idea how to deserialize the JSON and just ignore any empty arrays within the data? I'd like this to handle any empty arrays not just the example above for the object Venue.
New issue was found - 03/17/2018 <<
Hi, the converter below has been working perfectly but the server I am getting my json responses from threw another challenge. JSON.NET has had no problem retrieving this type of data:
"toasts": {
"total_count": 1,
"count": 1,
"auth_toast": false,
"items": [
{
"uid": 3250810,
"user": {
"uid": 3250810,
"account_type": "user",
"venue_details": [
],
"brewery_details": [
]
},
"like_id": 485242625,
"like_owner": false,
"created_at": "Wed, 07 Mar 2018 07:54:38 +0000"
}
]
},
Specifically the section that has venue_details. 99% of the responses come back with venue_details in this format:
"venue_details": [
],
But then I get this format suddenly:
"toasts": {
"total_count": 1,
"count": 1,
"auth_toast": false,
"items": [
{
"uid": 4765742,
"user": {
"uid": 4765742,
"account_type": "venue",
"venue_details": {
"venue_id": 4759473
},
"brewery_details": [
],
"user_link": "https://untappd.com/venue/4759473"
},
"like_id": 488655942,
"like_owner": false,
"created_at": "Fri, 16 Mar 2018 16:47:10 +0000"
}
]
},
Notice how venue_details now has a value and includes a venue_id.
So instead venue_details ends up looking like an object instead of an array. This ends up giving this exception:
JsonSerializationException: Cannot deserialize the current JSON object (e.g. {"name":"value"}) into type 'System.Collections.Generic.List`1[System.Object]' because the type requires a JSON array (e.g. [1,2,3]) to deserialize correctly.
In the converter code provided, that exception happens in this line with *s next to it:
public abstract class IgnoreUnexpectedArraysConverterBase : JsonConverter
{
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var contract = serializer.ContractResolver.ResolveContract(objectType);
if (!(contract is JsonObjectContract))
{
throw new JsonSerializationException(string.Format("{0} is not a JSON object", objectType));
}
do
{
if (reader.TokenType == JsonToken.Null)
return null;
else if (reader.TokenType == JsonToken.Comment)
continue;
else if (reader.TokenType == JsonToken.StartArray)
{
var array = JArray.Load(reader);
if (array.Count > 0)
throw new JsonSerializationException(string.Format("Array was not empty."));
return existingValue ?? contract.DefaultCreator();
}
else if (reader.TokenType == JsonToken.StartObject)
{
// Prevent infinite recursion by using Populate()
existingValue = existingValue ?? contract.DefaultCreator();
*** serializer.Populate(reader, existingValue); ***
return existingValue;
Any ideas how to add this additional handling to account for a flip like this between the JSON returning an object instead of an array?
Thanks,
Rick

Your problem is not that you need to ignore empty arrays. If the "items" array were empty, there would be no problem:
"items": [],
Instead your problem is as follows. The JSON standard supports two types of container:
The array, which is an ordered collection of values. An array begins with [ (left bracket) and ends with ] (right bracket). Values are separated by , (comma).
The object, which is an unordered set of name/value pairs. An object begins with { (left brace) and ends with } (right brace).
For some reason the server is returning an empty array in place of a null object. If Json.NET expects to encounter a JSON object but instead encounters a JSON array, it will throw the Cannot deserialize the current JSON array exception you are seeing.
You might consider asking whoever generated the JSON to fix their JSON output, but in the meantime, you can use the following converters to skip unexpected arrays when deserializing objects:
public class IgnoreUnexpectedArraysConverter<T> : IgnoreUnexpectedArraysConverterBase
{
public override bool CanConvert(Type objectType)
{
return typeof(T).IsAssignableFrom(objectType);
}
}
public class IgnoreUnexpectedArraysConverter : IgnoreUnexpectedArraysConverterBase
{
readonly IContractResolver resolver;
public IgnoreUnexpectedArraysConverter(IContractResolver resolver)
{
if (resolver == null)
throw new ArgumentNullException();
this.resolver = resolver;
}
public override bool CanConvert(Type objectType)
{
if (objectType.IsPrimitive || objectType == typeof(string))
return false;
return resolver.ResolveContract(objectType) is JsonObjectContract;
}
}
public abstract class IgnoreUnexpectedArraysConverterBase : JsonConverter
{
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var contract = serializer.ContractResolver.ResolveContract(objectType);
if (!(contract is JsonObjectContract))
{
throw new JsonSerializationException(string.Format("{0} is not a JSON object", objectType));
}
do
{
if (reader.TokenType == JsonToken.Null)
return null;
else if (reader.TokenType == JsonToken.Comment)
continue;
else if (reader.TokenType == JsonToken.StartArray)
{
var array = JArray.Load(reader);
if (array.Count > 0)
throw new JsonSerializationException(string.Format("Array was not empty."));
return null;
}
else if (reader.TokenType == JsonToken.StartObject)
{
// Prevent infinite recursion by using Populate()
existingValue = existingValue ?? contract.DefaultCreator();
serializer.Populate(reader, existingValue);
return existingValue;
}
else
{
throw new JsonSerializationException(string.Format("Unexpected token {0}", reader.TokenType));
}
}
while (reader.Read());
throw new JsonSerializationException("Unexpected end of JSON.");
}
public override bool CanWrite { get { return false; } }
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
Then, if empty arrays can appear in only one place in the object graph, you can add the converter to your model as follows:
public class Rootobject
{
[JsonConverter(typeof(IgnoreUnexpectedArraysConverter<Venue>))]
public Venue venue { get; set; }
}
But if, as you say, any object might be replaced with an empty array, you can use the non-generic IgnoreUnexpectedArraysConverter for all object types:
var resolver = new DefaultContractResolver(); // Cache for performance
var settings = new JsonSerializerSettings
{
ContractResolver = resolver,
Converters = { new IgnoreUnexpectedArraysConverter(resolver) },
};
var userInfo = JsonConvert.DeserializeObject<Rootobject>(jsonString, settings);
Notes:
The converter does not work with the TypeNameHandling or PreserveReferencesHandling settings.
The converter assumes that the object being deserialized has a default constructor. It the object has a parameterized constructor you will need to create a hardcoded converter to allocate and populate the object.
The converter throws an exception if the array is not empty, to ensure there is no data loss in the event of incorrect assumptions about the structure of the JSON. Sometimes servers will write a single object in place of a one-object array, and an array when there are zero, two or more objects. If you are also in that situation (e.g. for the "items" array) see How to handle both a single item and an array for the same property using JSON.net.
If you want the converter to return a default object instead of null when encountering an array, change it as follows:
else if (reader.TokenType == JsonToken.StartArray)
{
var array = JArray.Load(reader);
if (array.Count > 0)
throw new JsonSerializationException(string.Format("Array was not empty."));
return existingValue ?? contract.DefaultCreator();
}
Working sample .Net fiddle.

Related

Migrate Newtonsoft JsonConverter to JsonConverter<T>

I have the following interface
public interface IApiResult<TResult> : IApiResult
{
TResult Result { get; set; }
}
with a concrete class like this
public class ApiResult<TResult> : ApiResult, IApiResult<TResult>
{
public ApiResult( TResult result ) : base() {
Result = result;
}
public TResult Result { get; set; }
}
When I used Newtonsoft json library I used a JsonConverter to manage polimorphic serialization and deserialization this way
public override object ReadJson( JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer ) {
var obj = JObject.Load( reader );
var apiResult = new ApiResult.ApiResult().As<IApiResult>();
//Check if is an IApiResult<T>
if (obj.TryGetValue( nameof( ApiResult<object>.Result ), StringComparison.InvariantCultureIgnoreCase, out var jResult )) {
//Retrieve the result property type in order to create the proper apiResult object
var prop = objectType.GetProperty( nameof( ApiResult<object>.Result ) );
var value = jResult.ToObject( prop.PropertyType, serializer );
var rType = typeof( ApiResult<> ).MakeGenericType( prop.PropertyType );
apiResult = Activator.CreateInstance( rType ).As<IApiResult>();
prop.SetValue( apiResult, value );
}
//Set the messages
var jMessages = obj.GetValue( nameof( ApiResult.ApiResult.Messages ), StringComparison.InvariantCultureIgnoreCase ).ToObject<JObject[]>();
apiResult.Messages = DeserializeReasons( jMessages );
return apiResult;
}
How can I migrate this code to System.Text.Json?
UPDATE
My biggest problem is with the JObject.TryGetvalue function. This function would have returned a deserialized object that let me understood the type. Since that, I was only using some reflection to understand the type of ApiResult<T>.
With the actual UTF8JsonReader class I am only able to read token by token so I can not replicate the previous behavior.
Your question boils down to, Inside JsonConverter<T>.Read(), how can I scan forward in, or load the contents of, a Utf8JsonReader to determine the polymorphic type of object to deserialize without having to manually deserialize token by token as shown in the documentation example?
As of .NET 6 you have a couple options to accomplish this.
Firstly you can copy the Utf8JsonReader struct and scan forward in the copy until you find the property or properties you want. The original, incoming Utf8JsonReader will be unchanged and still point to the beginning of the incoming JSON value. System.Text.Json will always preload the entire JSON object, array or primitive to be deserialized before calling JsonConverter<T>.Read() so you can be certain the require values are present.
To do this, introduce the following extension methods:
public static partial class JsonExtensions
{
public delegate TValue? DeserializeValue<TValue>(ref Utf8JsonReader reader, JsonSerializerOptions options);
///Scan forward in a copy of the Utf8JsonReader to find a property with the specified name at the current depth, and return its value.
///The Utf8JsonReader is not passed by reference so the state of the caller's reader is unchanged.
///This method should only be called inside JsonConverter<T>.Read(), at which point the entire JSON for the object being read should have been pre-loaded
public static bool TryGetPropertyValue<TValue>(this Utf8JsonReader reader, string name, StringComparison comparison, JsonSerializerOptions options, out TValue? value) =>
reader.TryGetPropertyValue<TValue>(name, comparison, options, (ref Utf8JsonReader r, JsonSerializerOptions o) => JsonSerializer.Deserialize<TValue>(ref r, o), out value);
public static bool TryGetPropertyValue<TValue>(this Utf8JsonReader reader, string name, StringComparison comparison, JsonSerializerOptions options, DeserializeValue<TValue> deserialize, out TValue? value)
{
if (reader.TokenType == JsonTokenType.Null)
goto fail;
else if (reader.TokenType == JsonTokenType.StartObject)
reader.ReadAndAssert();
do
{
if (reader.TokenType != JsonTokenType.PropertyName)
throw new JsonException();
var currentName = reader.GetString();
reader.ReadAndAssert();
if (String.Equals(name, currentName, comparison))
{
value = deserialize(ref reader, options);
return true;
}
else
{
reader.Skip();
}
}
while (reader.Read() && reader.TokenType != JsonTokenType.EndObject);
fail:
value = default;
return false;
}
static void ReadAndAssert(ref this Utf8JsonReader reader)
{
if (!reader.Read())
throw new JsonException();
}
}
public static partial class ObjectExtensions
{
public static T ThrowOnNull<T>(this T? value) where T : class => value ?? throw new ArgumentNullException();
}
And now your Newtonsoft converter might be rewritten to look something like:
public class ApiResultConverter : System.Text.Json.Serialization.JsonConverter<IApiResult>
{
record MessagesDTO(Message [] Messages); // Message is the presumed type the array elements of ApiResult.ApiResult.Messages, which is not shown in your question.
public override IApiResult? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
IApiResult? apiResult = null;
if (reader.TryGetPropertyValue(nameof( ApiResult<object>.Result ), StringComparison.OrdinalIgnoreCase, options,
(ref Utf8JsonReader r, JsonSerializerOptions o) =>
{
var prop = typeToConvert.GetProperty( nameof( ApiResult<object>.Result ) ).ThrowOnNull();
return (Value : JsonSerializer.Deserialize(ref r, prop.PropertyType, o), Property : prop);
},
out var tuple))
{
var rType = typeof( ApiResult<> ).MakeGenericType( tuple.Property.PropertyType );
apiResult = Activator.CreateInstance( rType ).As<IApiResult>().ThrowOnNull();
tuple.Property.SetValue( apiResult, tuple.Value );
}
if (apiResult == null)
apiResult = new ApiResult.ApiResult().As<IApiResult>();
// Now consume the contents of the Utf8JsonReader by deserializing to MessagesDTO.
var dto = JsonSerializer.Deserialize<MessagesDTO>(ref reader, options);
apiResult.Messages = dto?.Messages ?? Array.Empty<Message>();
return apiResult;
}
public override void Write(Utf8JsonWriter writer, IApiResult value, JsonSerializerOptions options) => JsonSerializer.Serialize(writer, value, value.GetType(), options);
}
Notes:
This approach works well when scanning forward for a single simple property, e.g. a type discriminator string. If the type discriminator string is likely to be at the beginning of the JSON object it will be quite efficient. (This does not seem to apply in your case.)
JsonConverter<T>.Read() must completely consume the incoming token. E.g. if the incoming token is of type JsonTokenType.StartObject then, when exiting, the reader must be positioned on a token of type JsonTokenType.EndObject at the same depth. Thus if you only scan forward in copies of the incoming Utf8JsonWriter you must advance the incoming reader to the end of the current token by calling reader.Skip() before exiting.
Both Json.NET and System.Text.Json use StringComparison.OrdinalIgnoreCase for case-invariant property name matching, so I recommend doing so as well.
Secondly, you can load the contents of your Utf8JsonReader into a JsonDocument or JsonNode, query its properties, then deserialize to your final desired type with one of the JsonSerializer.Deserialzie() overloads that accepts a JSON document or node.
Using this approach with JsonObject in place of JObject, your converter might look something like:
public class ApiResultConverter : System.Text.Json.Serialization.JsonConverter<IApiResult>
{
public override IApiResult? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
var obj = JsonNode.Parse(ref reader, new JsonNodeOptions { PropertyNameCaseInsensitive = true })?.AsObject();
if (obj == null)
return null; // Or throw JsonException() if you prefer
IApiResult? apiResult = null;
if (obj.TryGetPropertyValue( nameof( ApiResult<object>.Result ), out var jResult ))
{
//Retrieve the result property type in order to create the proper apiResult object
var prop = typeToConvert.GetProperty( nameof( ApiResult<object>.Result ) ).ThrowOnNull();
var value = JsonSerializer.Deserialize(jResult, prop.PropertyType, options);
var rType = typeof( ApiResult<> ).MakeGenericType( prop.PropertyType );
apiResult = Activator.CreateInstance( rType ).As<IApiResult>();
prop.SetValue( apiResult, value );
}
if (apiResult == null)
apiResult = new ApiResult.ApiResult().As<IApiResult>();
//Set the messages
JsonObject? []? messages = obj[nameof( ApiResult.Messages )]?.AsArray()?.Select(i => i?.AsObject())?.ToArray();
apiResult.Messages = DeserializeReasons(messages); // Not shown in your question
return apiResult;
}
static JsonObject? [] DeserializeReasons(JsonObject? []? messages) => messages == null ? Array.Empty<JsonObject>() : messages;
public override void Write(Utf8JsonWriter writer, IApiResult value, JsonSerializerOptions options) => JsonSerializer.Serialize(writer, value, value.GetType(), options);
}
public static partial class ObjectExtensions
{
public static T ThrowOnNull<T>(this T? value) where T : class => value ?? throw new ArgumentNullException();
}
Notes:
This approach works well when you have multiple properties (possibly with complex values) to search for and load during the conversion process.
By loading the JsonObject with JsonNodeOptions.PropertyNameCaseInsensitive = true, all property name lookups in the deserialized JsonNode hierarchy will be case-insensitive (using StringComparer.OrdinalIgnoreCase matching as shown in the source).
Since your question does not include a compilable example, the above converters are untested.

Cannot Deserialize Recursively a Class Hierarchy Using JSON.NET

I have a class hierarchy like this:
class Rule { }
class Condition { List<Rule> Rules { get; set; } }
Forget about the remaining properties. I need to deserialize from a JSON string, using a custom JsonConverter. The problem is, I have code for each specific case, but I cannot have it ran recursively, for taking care of the Rules property, each of its elements can be a Condition too.
My code looks like this (ReadJson method):
var jo = JObject.Load(reader);
Rule rule = null;
if (jo["condition"] == null)
{
rule = new Rule();
//fill the properties for rule
}
else
{
rule = new Condition();
//I now want the converter to go through all the values in jo["rules"] and turn them into Rules or Conditions
}
What is the best way to achieve this? I tried to get the JSON for the remaining part, if the object is found to be a Condition:
var json = jo.GetValue("rule").ToString();
But I cannot deserialize it like this, it throws an exception:
var rules = JsonConvert.DeserializeObject<Rule[]>(json, this);
The exception is: JsonReaderException : Error reading JObject from JsonReader. Current JsonReader item is not an object: StartArray. Path '', line 1, position 1.
Any ideas?
You're not far from having it working. After you instantiate correct type of object based on the presence or absence of the condition property in the JSON, you can populate the instance using the serializer.Populate method. This should take care of the recursion automatically. You do need to pass a new JsonReader instance to Populate, which you can create using jo.CreateReader().
Here is what the converter should look like:
public class RuleConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return typeof(Rule).IsAssignableFrom(objectType);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var jo = JObject.Load(reader);
Rule rule = null;
if (jo["condition"] == null)
{
rule = new Rule();
}
else
{
rule = new Condition();
}
serializer.Populate(jo.CreateReader(), rule);
return rule;
}
public override bool CanWrite
{
get { return false; }
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
Here is a working example: https://dotnetfiddle.net/SHctMo

Ignore ExpandoObject Properties with JSON.Net Serializer Settings

Is it possible to ignore ExpandoObject properties, in particular those of Delegate type, when using JsonConvert(expando, formatting, serializerSettings)?
Essentially I'm trying to ignore all parsing of the func property in this example expando object:
//{
// func: () => {}
//}
Action func = () => {};
dynamic expando = new ExpandoObject();
expando.func = func;
// should be empty object {}
string json = JsonConvert(expando, formatting, serializerSettings);
The first thing I tried was overriding the converter. Unfortunately this doesn't work, as I see CanConvert called recursively for Action -> DelegateEntry -> some generic type -> RuntimeMethodInfo.
private class ExpandoObjectIgnoreConverter : ExpandoObjectConverter
{
public override bool CanConvert(Type objectType)
{
if (typeof(Delegate).IsAssignableFrom(objectType))
{
return false;
}
return base.CanConvert(objectType);
}
}
A method that works is using an error handler in serialization settings and a contract resolver. When I throw the error, all further processing of the property is ignored, i.e. Action -> DelegateEntry -> some generic type -> RuntimeMethodInfo. However, I'd like to do this more elegantly than throwing an exception if possible.
Error handler:
serializationSettings.Error = (sender, args) =>
{
if (args.ErrorContext.Error is InvalidCastException)
{
args.ErrorContext.Handled = true;
}
}
Contract resolver:
private class ExpandoObjectContractResolver : DefaultContractResolver
{
public override JsonContract ResolveContract(Type type)
{
if (typeof(Delegate).IsAssignableFrom(type))
{
throw new InvalidCastException();
}
else
{
return base.ResolveContract(type);
}
}
}
I'm using the edge library to script nodejs from within a C# process. I'm trying to remove functions from the returned javascript objects from within C#, as they are assigned a Delegate type that doesn't play nicely with JsonConvert.
ExpandoObjectConverter does not have any custom code to write an ExpandoObject. Instead it overrides JsonConverter.CanWrite to return false thereby allowing the expando to be serialized generically as an IDictionary<string, object>.
Thus you can override CanWrite and WriteJson() yourself to filter undesired key/value pairs before serialization:
public class FilteredExpandoObjectConverter : ExpandoObjectConverter
{
public override bool CanWrite { get { return true; } }
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var expando = (IDictionary<string, object>)value;
var dictionary = expando
.Where(p => !(p.Value is System.Delegate))
.ToDictionary(p => p.Key, p => p.Value);
serializer.Serialize(writer, dictionary);
}
}
Then use the converter in settings as follows:
var formatting = Formatting.Indented;
var serializerSettings = new JsonSerializerSettings
{
Converters = { new FilteredExpandoObjectConverter() },
};
var json = JsonConvert.SerializeObject(expando, formatting, serializerSettings);
Note this will only filter delegates values directly owned by an ExpandoObject. If you have a collection containing some delegates, or delegate-valued members in some POCO, they will not be filtered by this code.
Sample fiddle.

WebAPI Json Formatter considers "123" as being a valid json. How to avoid it?

I have the following ApiController method
public async Task<HttpResponseMessage> SetData(string user, [FromBody] JToken jsonPayload)
{
//
}
I'm doing some testing with malformed json values:
var malformedData = new List<string>
{
#"abcd {'value' : 0}",
#"{'abc' : }",
#"{'value' : value }",
#"{'value' : 0} abc",
"abc",
"123"
};
For all of these values I get in the controller jsonPayload being null. That's fine.
The only exception is the last string in the list 123, and the jsonPayload actually contains 123.
My Formatters config is:
config.Formatters.Clear();
config.Formatters.Add(new JsonMediaTypeFormatter());
config.Formatters.JsonFormatter.SerializerSettings.CheckAdditionalContent = true;
List<MediaTypeHeaderValue> unwantedTypes = config.Formatters.JsonFormatter.SupportedMediaTypes.Where(type => type.MediaType != "application/json").ToList();
foreach (MediaTypeHeaderValue unwantedType in unwantedTypes)
{
config.Formatters.JsonFormatter.SupportedMediaTypes.Remove(unwantedType);
}
What else can I set so that WebAPI considers this as a malformed json?

Model Binding a json array containing different object types in Asp.Net MVC

I have a json array containing integers and objects.
[1,2,3,{Name:"russia",Value:6},{Name:"usa",Value:"8"}]
I also have the following server-side class
class country {
string Name;
int Value;
}
How should I go about binding the json array to a server-side parameter ? I tried using a List<object> on server. It binds the integers fine but no country instances are created. Instead, primitive objects are created and added to the list.
Thanks.
You could try something like this:
Format your controller action to accept a List<int> for your integer values and a List<Country> for your Country objects.
public ActionResult Index(List<int> intValues, List<Country> countryValues)
Then build your JSON like so that it contains and array of integers and an array of country objects:
var postData = {
intValues: [1, 2, 3],
countryValues: [
{ Name: 'USA', Value: 6 },
{ Name: 'Russia', Value: 8 }
]
};
And perform a simple AJAX call to send the data
$(function() {
$.ajax({
type: 'POST',
url: "#Url.Action("Create")",
contentType: "application/json",
data: JSON.stringify(postData)
});
});
Okay, I solved it at last. I created a custom model binder derived from the default model binder to accomplish this. Here's the code.
public ActionResult Home(NonHomogeneousList input)
[ModelBinder(typeof(CustomModelBinder))]
class NonHomogeneousList : List<object>
public class CustomModelBinder : DefaultModelBinder
{
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
NonHomogeneousList model = (NonHomogeneousList)base.BindModel(controllerContext, bindingContext);
if (model.Members != null)
{
IModelBinder countryBinder = Binders.GetBinder(typeof(Country));
// find out if the value provider has the required prefix
bool hasPrefix = bindingContext.ValueProvider.ContainsPrefix(bindingContext.ModelName);
string prefix = (hasPrefix) ? bindingContext.ModelName + "." : "";
for (var i = 0; i < model.Count; i++)
{
var member = model.Members[i];
if (member.GetType().Equals(typeof(object)))
{
var subKey = CreateSubIndexName( prefix , i);
ModelBindingContext innerContext = new ModelBindingContext()
{
ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(null, typeof(Country)),
ModelName = subKey,
ModelState = bindingContext.ModelState,
PropertyFilter = bindingContext.PropertyFilter,
ValueProvider = bindingContext.ValueProvider,
};
var country = countryBinder.BindModel(controllerContext, innerContext);
model.Members[i] = country;
}
}
}
return model;
}
}
}

Resources