4

If I have the class

public class TestMe : JObject
{

}

How do I consume it within an API method?

public async Task<IHttpActionResult> UpdateMe(int accountId, [FromBody] TestMe sometest)
{
  Console.WriteLine(sometest);
  ...

When I try posting anything for the body, ex:

{
  "Bye": "string"
}

I get the following error before the Console.WriteLine line in the UpdateMe method is even hit:

"The parameters dictionary contains an invalid entry for parameter 'sometest' for method 'System.Threading.Tasks.Task`1[System.Web.Http.IHttpActionResult] UpdateMe(Int32, TestMe)' in '<somepath>.Controllers.MiscController'. The dictionary contains a value of type 'Newtonsoft.Json.Linq.JObject', but the parameter requires a value of type 'TestMe'."
Post Impatica
  • 14,999
  • 9
  • 67
  • 78
  • 3
    Why are you extending JObject? – ShanieMoonlight Feb 01 '21 at 15:15
  • 1
    One method I sometimes use when working with generic Json object is to handle them as IDictionary . Another way could be to simply read the raw request body as a string and deserialize it yourself. Perhaps you could achieve what you want with modelbinding but that will probably be a lot more work – Driven-it Feb 01 '21 at 15:32
  • There are many data types suited for getting dynamic data. A JObject isn't one of them. You are tying your API with a specific JSON library's object. Just use dynamic, or Dictionary, or any other type which suits you better. – FarhadGh Feb 02 '21 at 15:01
  • Sorry you accept my answer but gave the bounty to the other answer. Is that correct? – Maytham Fahmi Feb 02 '21 at 17:07
  • @maytham-ɯɐɥʇʎɐɯ yes, this was the first time I've ever done the bounty thing and it had a bounty button next to both of your answers so I thought I would be able to award to both, but after I awarded the first answer, your bounty button disappeared :( – Post Impatica Feb 03 '21 at 15:08
  • thanks, it is to sad not getting 250 bounty, but that is fine the other guy get it. thanks informing me – Maytham Fahmi Feb 03 '21 at 16:42

2 Answers2

4

the controller can't accept a class that inherits directly from the JObject class. (more info here). I assume that in your case you don't know what properties you will receive in your input JSON. These are possible solutions:

Accept only the JObject class in your controller.

[Route("{accountID}")]
[HttpPost]
public async Task<IHttpActionResult> UpdateMe(int accountId, [FromBody] JObject sometest)
{
    string test = sometest.ToString();

    return Ok(test);
}

you can work with the JObject class as you wish. Example - read more here.
This is one solution to the problem.


Another solution will be to read the JSON body as a raw JSON and do it whatever your needs may be:

[Route("{accountID}")]
[HttpPost]
public async Task<IHttpActionResult> UpdateMe(int accountId)
{
    string rawContent = string.Empty;
    using (var contentStream = await this.Request.Content.ReadAsStreamAsync())
    {
        contentStream.Seek(0, SeekOrigin.Begin);
        using (var sr = new StreamReader(contentStream))
        {
            rawContent = sr.ReadToEnd();
            // use raw content here
        }
    }

    return Ok(rawContent);
}

Another solution for an unknown input JSON is to accept the dynamic type in the controller, but personally, I don't recommend that. How to implement it read here. Why I don't like it, check this here.


Dharman
  • 30,962
  • 25
  • 85
  • 135
G.Dimov
  • 2,173
  • 3
  • 15
  • 40
2

The short answer NOT possible, IMO what you are asking for is NOT possible and has not thing to do with api.

IMO you have 3 options either use

  • dynamic type
  • custom object type
  • Dictionay type

As this answer mention,

Whatever is the reason you want to do that - the reason is simple: JObject implements IDictionary and this case is treated in a special way by Json.NET. If your class implements IDictionary - Json.NET will not look at properties of your class but instead will look for keys and values in the dictionary. So to fix your case you can do this

Let's get some details:

To prove my first point, try just to create a simple console app with newton.json with your input as string:

var input = "{\"Bye\": \"string\"}";

With dynamic it will works fine:

var result = JsonConvert.DeserializeObject<dynamic>(input);
Console.WriteLine(JsonConvert.SerializeObject(result));

Now with customize object like:

public class TestMe
{
    public string Bye { get; set; }
}

and

var result = JsonConvert.DeserializeObject<TestMe>(input);
Console.WriteLine(JsonConvert.SerializeObject(result));

Works fine as well.

Now lets take your approach:

public class TestMe : JObject
{
    
}

Testing it with following, it will break:

var result = JsonConvert.DeserializeObject<TestMe>(input);
Console.WriteLine(JsonConvert.SerializeObject(result));

Now lets try with Dictionary:

public class TestMe
{
    [JsonExtensionData]
    public Dictionary<string, JToken> MyProperties { get; set; } = new Dictionary<string, JToken>();
}

And test it

var result = JsonConvert.DeserializeObject<TestMe>(input);
Console.WriteLine(JsonConvert.SerializeObject(result));

Will also works fine.

Now I have presented 3 options.

Maytham Fahmi
  • 31,138
  • 14
  • 118
  • 137