8

I have the following method in my web api

public void Put(string id, [FromBody]IContent value) {
    //Do stuff
}

I'm using backbone js to send the following JSON to the server using fiddler the value is null:

{
    "id": "articles/1",
    "heading": "Bar",
    "$type": "BrickPile.Samples.Models.Article, BrickPile.Samples"
}

but if I add the $type property first in the JSON object the deserialization works fine, see:

{
 "$type": "BrickPile.Samples.Models.Article, BrickPile.Samples",
  "id": "articles/1",
  "heading": "Bar" 
}

is it possible to configure newtonsoft to check for the $type property anywhere in the object instead of the first property or can I configure backbone so it always adds the $type property first in the JSON object?

CodesInChaos
  • 106,488
  • 23
  • 218
  • 262
marcus
  • 9,616
  • 9
  • 58
  • 108
  • Look at @Levi's response, he's the security guy for the web team. – RickAndMSFT Apr 10 '13 at 00:51
  • 2
    Its completely insane that it works this way in JSON.NET and Microsofts built-in stuff... In many JSON-libraries, like Jackson in JAVA, it is NOT guaranteed that the map (which u can build in Jackson) is ordered. Actually, its a Map, and thus no order is maintained. So, you have no control of where the $type or __type appears in the JSON. Sometime it first, and works, sometimes its not. Thus, its impossible to truts this functionality... – Ted Aug 08 '14 at 12:04
  • This is a basic type error in the library. If the library wants to rely on the order, it has to use an ordered data type, e.g. a 2-element array, where the first element was the serialized class name and the second element the object data. A JSON object is by definition unordered, so the way it is now is a bug in the library. – 00prometheus May 22 '19 at 15:54

4 Answers4

4

I would very strongly recommend against configuring any serializer (including JSON.NET) to read the object type from the incoming payload. This has historically been the cause of a large number of vulnerabilities in web applications. Instead, change the public entry point to your action to take the actual type as a bound parameter, then delegate to an internal testable method if desired.

Levi
  • 32,628
  • 3
  • 87
  • 88
  • My problem is that I don't know the actual type, the only thing I know is that it implements the IContent interface. I can also say that this is not a public API, it requires that your are authenticated. – marcus Mar 22 '13 at 16:45
4

First, AFAIK, the code of Json.NET is optimized to avoid holding the whole object in memory just to read its type. So it's better to place $type as the first property.

Second, you can write your own JsonConverter which reads first to JObject (using Load method), manually reads $type property, gets type from serializer's SerializationBinder, creates the value and populates it from JObject.

Third, regarding security. While Json.NET's $type may sound like a good idea, it's often not. It allows Json.NET to create any object type from any assembly just by writing its type in JSON file. It's better to use custom SerializationBinder with a dictionary which allows only types which you specify. You can find an example in my private framework (it also supports getting values for $type from JsonObjectAttribute):

https://github.com/Athari/Alba.Framework/blob/742ff1aeeb114179a16ca42667781944b26f3815/Alba.Framework/Serialization/DictionarySerializationBinder.cs

(This version uses some methods from other classes, but they're trivial. Later commits made the class more complex.)

Athari
  • 33,702
  • 16
  • 105
  • 146
3

I had kind of the same question apparently, and someone found an answer. Not sure whats the appropriate way to share an answer and give him ALL the credit, but this is the link: Newtonsoft JSON.net deserialization error where fields in JSON change order

and this is the guy: https://stackoverflow.com/users/3744182/dbc

Community
  • 1
  • 1
Shy Peleg
  • 605
  • 1
  • 6
  • 9
1

This will work in backbone, but I don't know if every browser will behave the same. There's no guarantee, basically, that every browser will keep the items in the order which they are added.


MyModel = Backbone.Model.extend({

  // ...

  toJSON: function(){
    // build the "$type" as the first parameter
    var json = {"$type": "BrickPile.Samples.Models.Article, BrickPile.Samples"};
    // get the rest of the data
    _.extend(json, Backbone.Model.prototype.toJSON.call(this));
    // send it back, and hope it's in the right order
    return json;
  }


});

You're better of getting NewtonSoft's JSON deserializer to work without needing it in a specific position, though. Hopefully that will be possible.

Derick Bailey
  • 72,004
  • 22
  • 206
  • 219
  • that worked pretty good, the $type property already exists in the backbone Model, so is it possible to to get "BrickPile.Samples.Models.Article, BrickPile.Samples" from the current model instead of hard code the string? – marcus Mar 22 '13 at 13:40
  • 1
    I guess this.get('$type') will do the trick – marcus Mar 22 '13 at 13:51
  • Borth DataContractSerializer (Microsoft) and json.net REQUIRES that the $type (or __type in MS) is the first property, otherwise it will epic fail. Why it is this way is completely incomprehensable, but that is the way it is... – Ted Aug 08 '14 at 12:01