21

I'm implementing a Web API 2 service that uses JSON.NET for serialization.

When I try to PUT ( deseralize ) updated json data, the abstract class is not present meaning it didn't know what to do with it so it did nothing. I also tried making the class NOT abstract and just inheriting from it and then each PUT deseralized to the base class rather than the derrived class missing the properties of the derrived class.

Example:

public class People
{
      // other attributes removed for demonstration simplicity

      public List<Person> People { get;set; }
}

public abstract class Person
{
      public string Id {get;set;}
      public string Name {get;set;}
}

public class Employee : Person 
{
      public string Badge {get;set;}
}

public class Customer : Person
{
     public string VendorCategory {get;set;}
}

with my web api configured to do typename handling:

public static void Register(HttpConfiguration config)
{
     config.Formatters.JsonFormatter.SerializerSettings.TypeNameHandling = 
            TypeNameHandling.Objects;
}

then I PUT the JSON like:

{
     people: [{
          name: "Larry",
          id: "123",
          badge: "12345",
          $type: "API.Models.Employee, API"
     }]
}

to the web api method:

public HttpResponseMessage Put(string id, [FromBody]People value)
{
      people.Update(value); // MongoDB Repository method ( not important here )
      return Request.CreateResponse(HttpStatusCode.OK);
}

but the output when inspecting value is always:

People == { People: [] }

or if non-abstract:

People == { People: [{ Name: "Larry", Id: "123" }] }

missing the inherrited property. Anyone ran into this problem and come up with anything?

amcdnl
  • 8,470
  • 12
  • 63
  • 99
  • I've never used `TypeNameHandling` but used objects with similar definitions and had no problems with deserialization. It makes me think you should just remove the `TypeNameHandling` nonsense because I don't see how it adds any value and in this case maybe it's having unwanted side effects. – evanmcdonnal Dec 05 '13 at 19:30
  • 1
    @evanmcdonnal `TypeNameHandling` is no nonsense! It is required when you have JSON of a derived class, serialize it to the base class and still want an instance of the derived class. – user3038092 Dec 05 '13 at 19:40
  • @evanmcdonnal - I tried w/o the TypeHandling and had same result. – amcdnl Dec 05 '13 at 19:40
  • @user3038092 ok but I don't see how that makes sense. If have json of a derived class and want an instance of a derived class then I would call `JsonConvert.DeserializeObject(json)`. I do think I overlooked one thing, in order to make that work smoothly you'd probably need an `Employees` class that has `List` rather than `List`. – evanmcdonnal Dec 05 '13 at 19:41
  • 1
    @evanmcdonnal And if he has multiple dervied classes and needs to check the actual type at runtime? You can't use your code then. Also the deserialization code is not in his hand, it's behind the curtains in web api. The TypeNameHandling is for keeping track of the actual type within the Json. – user3038092 Dec 05 '13 at 19:43
  • 1
    @user3038092 I guess I stand corrected. However, I still think (from personal experience working on several API's and clients) if you design your system well you should never have to use that feature. – evanmcdonnal Dec 05 '13 at 19:45
  • 4
    @evanmcdonnal That is a very poorly thing to be said: "if you design your system well you should never have to use that feature". There are many many possibilities on how to design your API, some involving this feature, which are all "right". There is no clear right or wrong. – user3038092 Dec 05 '13 at 19:56
  • @user3038092 there is no right or wrong but there best practices and some implementations are more clean and simple than others. Given I've been using json.NET daily for the last 2 years and never used this feature I will stand behind that statement that you can probably do things more simply without it. – evanmcdonnal Dec 05 '13 at 19:59
  • http://stackoverflow.com/questions/30171579/type-is-an-interface-or-abstract-class-and-cannot-be-instantiated-innerexceptio?noredirect=1#comment48450423_30171579 I have a seem problem – Ignacio Chiazzo May 12 '15 at 17:07
  • Possible duplicate of [Json.Net Serialization of Type with Polymorphic Child Object](https://stackoverflow.com/questions/29528648/json-net-serialization-of-type-with-polymorphic-child-object) – N. Kudryavtsev Feb 07 '18 at 17:27

5 Answers5

25

The $type function has to be the first attribute in the object.

In the above example I did:

 {
   people: [{
      name: "Larry",
      id: "123",
      badge: "12345",
      $type: "API.Models.Employee, API"
   }]
 }

after moving $type to the top like:

 {
   people: [{
      $type: "API.Models.Employee, API",
      name: "Larry",
      id: "123",
      badge: "12345"
   }]
 }

the serializer was able to deseralize the object to the correct cast. Gotta love that!

amcdnl
  • 8,470
  • 12
  • 63
  • 99
  • 5
    alternatively, you could use `config.Formatters.JsonFormatter.SerializerSettings.TypeNameHandling = TypeNameHandling.Auto;` when serializing – jungle_mole Apr 11 '15 at 02:06
  • 1
    @zloidooraque that only works when the server creates them, if you create these items on the client it needs to know the type when you POST – amcdnl Apr 14 '15 at 15:33
  • Both question and answer saved my day. Thanks! – Alfredo A. Apr 25 '16 at 13:32
1

I have tried your scenario now and it works fine. But I did notice that you are missing a , (comma) after the id property in your json input.

I figured this out by using the following ModelState validity check in my action which then showed the error in my request payload. This could be useful to you too:

if (!ModelState.IsValid)
{
    return Request.CreateErrorResponse(HttpStatusCode.BadRequest, this.ModelState);
}
Kiran
  • 56,921
  • 15
  • 176
  • 161
  • The comma was a typo in the example demonstration. – amcdnl Dec 05 '13 at 19:51
  • Does the modelstate show any error information? what is the property name of your People class's list of persons property? – Kiran Dec 05 '13 at 19:55
  • Thanks for the snapshot, but i see that the `Values` has data in it...did you try the piece of code which i pasted above..it would show you the error information? – Kiran Dec 05 '13 at 20:16
  • Ya it actually says: {"Could not create an instance of type API.Models.People. Type is an interface or abstract class and cannot be instantiated. Path 'people[0].name', line 1, position 323."} – amcdnl Dec 05 '13 at 20:20
1

I know this post is old now and the answer has been marked, but I thought my solution might be helpful....

Try adding the JsonProperty attribute to the properties on your abstract class.

    using JTC.Framework.Json;
    ...
    public class People
    {
        // other attributes removed for demonstration simplicity

        public List<Person> People { get;set; }
    }

    public abstract class Person
    {
          [JsonProperty()]
          public string Id {get;set;}

          [JsonProperty()]
          public string Name {get;set;}
    }

    public class Employee : Person 
    {
          public string Badge {get;set;}
    }

    public class Customer : Person
    {
         public string VendorCategory {get;set;}
    }
Dave Russell
  • 141
  • 1
  • 8
  • The question is how to deserialize JSON when `Badge` and `VendorCategory` are marked with `JsonProperty` attribute too, and how not to lose these inherited properties. – N. Kudryavtsev Feb 07 '18 at 15:44
1

JsonSubTypes library allows specifying which subclass of the given class should be used to deserialize into via attributes just like Jackson library in Java does. To be more specific, you can:

  • Choose a field and specify its value for each subclass, or
  • Specify fields present only in certain subclass.
N. Kudryavtsev
  • 3,556
  • 1
  • 26
  • 30
-1

I had a very similar issue. What worked for me was to add a default constructor that initializes the objects in your class. Make sure you initialize each object. In your case, you need to add the constructor to the People class.

public class People
{
  public People()
  {
     People = new List<Person>();
  }
  public List<Person> People { get;set; }
}

Also, this seems to be an all-or-nothing shot. If you do not initialize any contained objects, none of them will contain values.

NickT
  • 55
  • 2