1

Json.Net is not deserializing the object it receives into the proper derivatives of my Control class. (Please see the following explanation of the problem. Also please note, I feel this is the minimum amount of code required to explain the issue. Thanks ahead of time for reviewing this problem.)

I am trying to serialize/deserialize the following class(es) into/from JSON.

public class Page {
    public Guid Id { get; set; }
    public Guid CustomerId { get; set; }
    public IList<Control> Controls { get; set; }
}

And here is the Control class:

public class Control : ControlBase
{
    public override Enums.CsControlType CsControlType { get { return Enums.CsControlType.Base; } }
}

And here is the ControlBase abstract class:

public abstract class ControlBase
{
    public Guid Id { get; set; }

    public virtual Enums.CsControlType CsControlType { get; }

    public Enums.ControlType Type { get; set; }

    public string PropertyName { get; set; }

    public IList<int> Width { get; set; }

    public string FriendlyName { get; set; }

    public string Description { get; set; }
}

And here is the OptionsControl which is derived from Control:

public class OptionsControl : Control
{
    public override Enums.CsControlType CsControlType { get { return Enums.CsControlType.OptionsControl; } }

    public IDictionary<string, string> Options;
}

When the OptionsControl is serialized, the JSON looks like the following. Note that Json.Net adds the $type property, so that (supposedly) it can deserialize the control back into an OptionsControl.

              {  
                 "options":{  
                    "TN":"TN"
                 },
                 "csControlType":4,
                 "id":"00000000-0000-0000-0000-000000000000",
                 "type":4,
                 "propertyName":"addresses[0].state",
                 "width":[  
                    2,
                    2,
                    6
                 ],
                 "friendlyName":"State",
                 "description":null,
                 "$type":"MyApp.Infrastructure.Core.Models.UI.Controls.OptionsControl, MyApp.Infrastructure.Core"
              }

However, when I try to deserialize the Page (which contains the base Controls and OptionsControls), all controls are deserialized into the base Control and the options property (which includes the list of States, above) is ignored.

This is how I try to deserialize (and update) the Page object I receive via a WebAPI service:

    [HttpPut]
    [ActionName("UpdatePage")]
    public async Task<CommandResult> UpdatePageAsync([FromBody]Object page)
    {
        try
        {
            // Deserialize the page into the correct object
            // When I Debug.WriteLine the page.ToString(), the page still has the $type property at this point (before it is deserialized).
            var dsPage = return JsonConvert.DeserializeObject<Page>(page.ToString(), new JsonSerializerSettings
            {
                TypeNameHandling = TypeNameHandling.All
            });

            // The dsPage passed in here does not have any OptionsControls.
            // At this point, they have all been deserialized into the base Control, unfortunately.
            return await _uiCommandFacade.UpdatePageAsync(dsPage, msg);
        }
        catch (Exception ex)
        {
            return new CommandResult()
            {
                ErrorType = ErrorType.ControllerError,
                DeveloperMessage = $"Unable to update page",
                Exception = ex
            };
        }
    }

As you can see, I am using TypeNameHandling.All (as described in the docs and mentioned many times on SO) for the deserialization (just as I am during the serialization, which is what generates the $type property in the JSON). However, when I deserialize the Page object, any OptionsControls in the Page object are deserialized into regular base Controls, so the Options property is ignored (and thus my list of States is not updated / thrown away).

How do I get Json.Net to properly deserialize my Controls and its derivatives?

Targaryen
  • 1,081
  • 2
  • 17
  • 30

2 Answers2

5

It looks like the problem might be due to the fact that the $type metadata property is not the first property in the JSON for your control. Normally, Json.Net needs this property to be first in order to recognize it. Try adding

MetadataPropertyHandling = MetadataPropertyHandling.ReadAhead

to your settings when deserializing. That should allow Json.Net to find the $type property later in the JSON.

Brian Rogers
  • 125,747
  • 31
  • 299
  • 300
  • Yes, after lots of hours this was also the solution for me. However, I first had to figure out *where* to set the value. Your answer combined with this one was my rescue: https://stackoverflow.com/a/13274791/3812472 – SVSchmidt Nov 18 '18 at 08:49
0

I think you should setup your configuration in your web-api startup class.

Try in the startup.cs file something like

        GlobalConfiguration.Configuration.Formatters.Clear(); // or remove just the json one 

        var jsonformatter = new System.Net.Http.Formatting.JsonMediaTypeFormatter()
        {
            SerializerSettings = new Newtonsoft.Json.JsonSerializerSettings()
            {
                // all your configurations
                TypeNameHandling = Newtonsoft.Json.TypeNameHandling.All
            }
        };

        GlobalConfiguration.Configuration.Formatters.Add(jsonformatter);

I think you are losing the informations because you get a deserialized Object item (page) in the method, then you serialize it again by calling the page.tostring() method, and then you deserialize it again with your serializer properties.

Leonardo Festa
  • 101
  • 1
  • 6