4

I am using Newtonsoft JSON in my MVC based off the example cited here: Setting the Default JSON Serializer in ASP.NET MVC.

My data contract class looks something like:

[DataContract]
public MyContractClass
{
    public MyContractClass()
    {
        this.ThisPropertyFails = new List<ClassDefinedInAnotherAssembly>();
    }

    [DataMember(EmitDefaultValue = false, Name = "thisPropertyIsFine")]
    public string ThisPropertyIsFine { get; set; }

    [DataMember(EmitDefaultValue = false, Name = "thisPropertyFails")]
    public IList<ClassDefinedInAnotherAssembly> ThisPropertyFails { get; internal set; }
}

The code I'm specifically using to de-serialize looks like this:

public override IValueProvider GetValueProvider(ControllerContext controllerContext)
{
    if (controllerContext == null)
    {
        throw new ArgumentNullException("controllerContext");                
    }

    if (!controllerContext.HttpContext.Request.ContentType.StartsWith("application/json", StringComparison.OrdinalIgnoreCase))
    {
        return null;                
    }

    var reader = new StreamReader(controllerContext.HttpContext.Request.InputStream);
    var bodyText = reader.ReadToEnd();

    if (String.IsNullOrEmpty(bodyText))
    {
        return null;
    }
    else
    {
        JsonSerializerSettings serializerSettings = new JsonSerializerSettings();
        serializerSettings.Converters.Add(new StringEnumConverter());
        serializerSettings.Converters.Add(new ExpandoObjectConverter());

        DictionaryValueProvider<object> result = new DictionaryValueProvider<object>(JsonConvert.DeserializeObject<ExpandoObject>(bodyText, serializerSettings), CultureInfo.CurrentCulture);
        return result;
    }

    //return String.IsNullOrEmpty(bodyText) ? null : new DictionaryValueProvider<object>(JsonConvert.DeserializeObject<ExpandoObject>(bodyText, new ExpandoObjectConverter(), new StringEnumConverter()), CultureInfo.InvariantCulture);
}

However, in the MVC action, ModelState.IsValid is false, and looking at the errors, I see this:

{"The parameter conversion from type 'System.Collections.Generic.List`1[[System.Object, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]' to type 'OtherAssembly.ClassDefinedInAnotherAssembly' failed because no type converter can convert between these types."}

Does anyone have any idea what is going on here? This same class works fine with my WebApi project (which is 'OtherAssembly' in this example).

Edit #1

Using the code directly, with the known type, does indeed work. So it's something to do with properties under ExpandoObject. For example, this code:

    JsonSerializerSettings serializerSettings = new JsonSerializerSettings();
    serializerSettings.Converters.Add(new StringEnumConverter());
    serializerSettings.Converters.Add(new ExpandoObjectConverter());

    JsonSerializer serializer = JsonSerializer.Create(serializerSettings);

    using (StreamReader streamReader = new StreamReader(controllerContext.HttpContext.Request.InputStream))
    {
        using (JsonReader jsonReader = new JsonTextReader(streamReader))
        {
            var resultAbc = serializer.Deserialize(jsonReader, typeof(MyContractClass));
        }
    }

Works just fine.

Edit #2

It appears I'm not the only person to have this issue. Anyone using MVC and using the oft-cited source code to use Newtonsoft makes it impossible to de-serialize complex sub-properties: http://tech.pro/q/34/using-jsonnet-to-deserialize-incoming-json-in-aspnet-mvc

No idea why that code is so popular if it doesn't even work after 1 level in the contract?

8
  • Maybe you should extend your own JsonConverter to make explicit convertion of you OtherAssembly.ClassDefinedInAnotherAssembly ? Commented May 21, 2014 at 17:59
  • @Ksven I probably could do that but there are hundreds, if not thousands of classes, so it seems stupid to have to do that for all of them? That makes no sense. Commented May 21, 2014 at 18:06
  • You have thousands of classes to serialize? Commented May 21, 2014 at 19:08
  • @Bob. Haha I guess thousands was an exaggeration, but I do have over 300 right now, and it's growing (enterprise application). It's not practical to write specific converters for all of them, and I shouldn't even have to, as this exact same stuff works in WebApi with Newtonsoft with no issues. Commented May 21, 2014 at 19:10
  • Hopefully, you are only serializing relevant data and not entire classes because data is in those classes... I've always been using straight deserialization of objects with MyObjectType myObj = JsonConvert.DeserializeObject<MyObjectType>(temp); Commented May 21, 2014 at 19:14

2 Answers 2

0

Too much overhead. Try :

public MyContractClass
{

    [JsonProperty("thisPropertyIsFine")]
    public string ThisPropertyIsFine { get; set; }

    [JsonProperty("thisPropertyFails")]
    public ClassDefinedInAnotherAssembly[] ThisPropertyFails { get; set; }
}

Serialize and deserialize this way

/* as Bob mentioned */
var deserialized = JsonCovert.DeserializeObject<MyContractClass>(jsonString);
var serialized = JsonCovert.SerializeObject(myContractClassInstance);

Json.NET does not serialize non-public members. See this Answer to change this behavior.

Sign up to request clarification or add additional context in comments.

1 Comment

Having an IList or an array with an internal set is normal, as per CA/SA practices in C#. Furthermore, JSON.NET works just fine with it, as it doesn't need to set it, all it needs to do is call IEnumerator.Add, so you're wrong. And the issue is not about that at all. The issue is de-serializing complex objects from a different assembly automatically, which is still not possible.
0

The error message "The parameter conversion from type 'X' to type 'Y' failed because no type converter can convert between these types." is coming from the ASP.Net Model Binding process, not from Json.Net's deserialization process.

To fix this error you just need to create a custom model binder for whatever type is giving you the problem (ClassDefinedInAnotherAssembly). Here is an example I'm currently using for one of my custom types called Money:

public class MoneyModelBinder : DefaultModelBinder
{
    public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
    {
        // get the result
        var valueProviderResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);

        Money value;
        // if the result is null OR we cannot successfully parse the value as our custom type then let the default model binding process attempt it using whatever the default for Money is
        return valueProviderResult == null || !Money.TryParse(valueProviderResult.AttemptedValue, out value) ? base.BindModel(controllerContext, bindingContext) : value;
    }
}

TryParse from Money type:

public static bool TryParse(string value, out Money money)
{
    money = null;

    if (string.IsNullOrWhiteSpace(value))
        return false;

    decimal parsedValue;
    if (decimal.TryParse(value, out parsedValue) == false)
        return false;

    money = parsedValue;

    return true;
}

You wire up model binders in Global.asax.cs under Application_Start():

protected void Application_Start()
{
    // custom model binders
    ModelBinders.Binders.Add(typeof(Money), new MoneyModelBinder());
}

Any time the Model Binding process runs across a type of Money it will call this class and attempt to do the conversion.


You may want to remove your Json.Net ValueProviderFactory temporarily so its a non-factor while you resolve the model binding issue. There are lots of Json.Net ValueProviderFactorys out there. Not all are equal and may be causing an issue like not serializing dictionaries/arrays or not fully iterating an objects entire hierarchy before giving up.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.