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?

10
  • 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. Commented Dec 5, 2013 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. Commented Dec 5, 2013 at 19:40
  • 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. Commented Dec 5, 2013 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. Commented Dec 5, 2013 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. Commented Dec 5, 2013 at 19:56

5 Answers 5

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!

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

2 Comments

alternatively, you could use config.Formatters.JsonFormatter.SerializerSettings.TypeNameHandling = TypeNameHandling.Auto; when serializing
@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
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);
}

4 Comments

The comma was a typo in the example demonstration.
Does the modelstate show any error information? what is the property name of your People class's list of persons property?
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?
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."}
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;}
    }

1 Comment

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.
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.

Comments

-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.

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.