I have an ASP.NET application that uses the Azure AD Graph API. Often, when an invalid operation is performed against the Graph API, an exception is thrown.
The following code shows an invalid Graph API call that would trigger an exception:
// Query the Azure AD User
var userToUpdate = await activeDirectoryClient.Users.GetByObjectId("user#domain.net").ExecuteAsync();
// Set given name to an empty string (not allowed)
userToUpdate.GivenName = "";
try
{
// Update the user in Azure AD
await userToUpdate.UpdateAsync();
}
catch (Exception e)
{
// Return exception message
}
The message of the inner exception is a JSON string with forward slashes before each quotation mark. It looks something like this:
"{\"odata.error\":{\"code\":\"Request_BadRequest\",\"message\":{\"lang\":\"en\",\"value\":\"Invalid value specified for property 'givenName' of resource 'User'.\"},\"values\":[{\"item\":\"PropertyName\",\"value\":\"givenName\"},{\"item\":\"PropertyErrorCode\",\"value\":\"InvalidValue\"}]}}"
Attaching a screenshot of the Locals window where the exception message is found:
I would like to convert this JSON to a .NET object to return informative error details. I am using the JSON.NET library for this, and I am assuming that the JSON will deserialize to an ODataError object:
var error = Newtonsoft.Json.JsonConvert.DeserializeObject<ODataError>(e.InnerException.Message);
However, the deserialized object always has a value of null, which means that the conversion is not working as expected.
That being said, what class should the above JSON string map to? Also, should I be removing the forward slashes from the string for proper deserialization?
The reason you've got null after deserialization is your JSON object properties names differs from Microsoft.Azure.ActiveDirectory.GraphClient.ODataError class properties names - "odata.error" property can not be deserialized to Error property of Microsoft.Azure.ActiveDirectory.GraphClient.ODataError
As workaround I've added my own types for correct deserialization:
internal class ODataError
{
[JsonProperty("odata.error")]
public ODataErrorCodeMessage Error { get; set; }
}
internal class ODataErrorCodeMessage
{
public string Code { get; set; }
public ODataErrorMessage Message { get; set; }
public List<ExtendedErrorValue> Values { get; set; }
}
internal class ExtendedErrorValue
{
public string Item { get; set; }
public string Value { get; set; }
}
internal class ODataErrorMessage
{
public string Lang { get; set; }
public string Value { get; set; }
}
After that JSON message was properly deserialized:
...
try
{
await ADClient.Users.AddUserAsync(newUser);
return Result.Ok();
}
catch (DataServiceRequestException ex)
{
var innerException = ex.InnerException;
var error = JsonConvert.DeserializeObject<ODataError>(innerException.Message);
return Result.Fail(new Error(error.Error.Message.Value, error.Error.Code, ex));
}
Related
The service I'm dealing returning different objects nested into a generic object. Here are sample mockup results from service
{"date":1591430887.591481,"results":[{"identity":"result_type_a","result":{"attr_a":1591427634}}]}
{"date":1591430887.591481,"results":[{"identity":"result_type_b","result":{"attr_b":1591427634,"attr_bb":3591457634}}]}
{"date":1591430887.591481,"results":[{"identity":"result_type_c","result":{"attr_c":1591427634,"attr_cc":3591457634,"attr_cc":59634}},{"identity":"result_type_d","result":{"attr_d":"rfrvr","attr_dd":"ytur"}}]}
I tried creating a generic object with declaring result attribute as string. And planning that I could deserialize to the returning json to that generic object, check identity attribute and deserilize the string in result attribute to specific object type.
Here is the object structure
public class GeneralResponse
{
[JsonProperty("date")]
public double Date { get; set; }
[JsonProperty("results")]
public List<GenericResults> Results { get; set; }
}
public class GenericResults
{
[JsonProperty("identity")]
public string Identity { get; set; }
[JsonProperty("result")]
public string Result { get; set; }
}
To serialize/deserialize I'm using Newtonsoft library and the code is below
public static GeneralResponse SerializeResponse(string response)
{
return JsonConvert.DeserializeObject<GeneralResponse>(response);
}
Unfortunately I got following exception while deserializing generic object.
"Unexpected character encountered while parsing value: {. Path 'results[0].result', line 1, position 71."
If I declare Result property of GenericResult as object as below
public class GenericResults
{
[JsonProperty("identity")]
public string Identity { get; set; }
[JsonProperty("result")]
public object Result { get; set; }
}
I can pass first serialization and make the second serialization without getting any exception.
string inner_object = response.Result.ToString();
switch (type)
{
case ResponseTypes.type_A: return JsonConvert.DeserializeObject<ObjectTypeA>(inner_object);
case ResponseTypes.type_B: return JsonConvert.DeserializeObject<ObjectTypeB>(inner_object);
case ResponseTypes.type_C: return JsonConvert.DeserializeObject<ObjectTypeC>(inner_object);
default: throw new Exception("Unknown Response Type");
}
But returned object does not contain the data.
I would appreciate any help about modeling this algorithm. Thanks in advance.
When you use string with JSON.NET serialization it is not actually using the JSON string but tries to parse a JSON string value.
You could use JObject instead, which is JSON.NETs wrapper for JSON objects. The JObject can then be used to deserialize into a type or by used to access JSON properties directly. Like this:
switch (type)
{
case ResponseTypes.type_A: return response.Result.ToObject<ObjectTypeA>(inner_object);
case ResponseTypes.type_B: return response.Result.ToObject<ObjectTypeB>(inner_object);
case ResponseTypes.type_C: return response.Result.ToObject<ObjectTypeC>(inner_object);
default: throw new Exception("Unknown Response Type");
}
You could use a Dictionary to store the result, which looks to be polymorphic. For example,
public partial class GeneralResponse
{
[JsonProperty("date")]
public double Date { get; set; }
[JsonProperty("results")]
public ResultElement[] Results { get; set; }
}
public partial class ResultElement
{
[JsonProperty("identity")]
public string Identity { get; set; }
[JsonProperty("result")]
public Dictionary<string,object> Result { get; set; }
}
Working Demo
Output Sample
We've got an API, which simply posts incoming JSON documents to a message bus, having assigned a GUID to each. We're upgrading from .Net Core 2.2 to 3.1 and were aiming to replace NewtonSoft with the new System.Text.Json library.
We deserialise the incoming document, assign the GUID to one of the fields and then reserialise before sending to the message bus. Unfortunately, the reserialisation is failing with the exception Operation is not valid due to the current state of the object.
Here's a controller that shows the problem:-
using System;
using System.Net;
using Project.Models;
using Microsoft.AspNetCore.Mvc;
using System.IO;
using System.Text;
using System.Text.Json;
namespace Project.Controllers
{
[Route("api/test")]
public class TestController : Controller
{
private const string JSONAPIMIMETYPE = "application/vnd.api+json";
public TestController()
{
}
[HttpPost("{eventType}")]
public async System.Threading.Tasks.Task<IActionResult> ProcessEventAsync([FromRoute] string eventType)
{
try
{
JsonApiMessage payload;
using (StreamReader reader = new StreamReader(Request.Body, Encoding.UTF8)) {
string payloadString = await reader.ReadToEndAsync();
try {
payload = JsonSerializer.Deserialize<JsonApiMessage>(payloadString);
}
catch (Exception ex) {
return StatusCode((int)HttpStatusCode.BadRequest);
}
}
if ( ! Request.ContentType.Contains(JSONAPIMIMETYPE) )
{
return StatusCode((int)HttpStatusCode.UnsupportedMediaType);
}
Guid messageID = Guid.NewGuid();
payload.Data.Id = messageID.ToString();
// we would send the message here but for this test, just reserialise it
string reserialisedPayload = JsonSerializer.Serialize(payload);
Request.HttpContext.Response.ContentType = JSONAPIMIMETYPE;
return Accepted(payload);
}
catch (Exception ex)
{
return StatusCode((int)HttpStatusCode.InternalServerError);
}
}
}
}
The JsonApiMessage object is defined like this:-
using System.Text.Json;
using System.Text.Json.Serialization;
namespace Project.Models
{
public class JsonApiMessage
{
[JsonPropertyName("data")]
public JsonApiData Data { get; set; }
[JsonPropertyName("included")]
public JsonApiData[] Included { get; set; }
}
public class JsonApiData
{
[JsonPropertyName("type")]
public string Type { get; set; }
[JsonPropertyName("id")]
public string Id { get; set; }
[JsonPropertyName("attributes")]
public JsonElement Attributes { get; set; }
[JsonPropertyName("meta")]
public JsonElement Meta { get; set; }
[JsonPropertyName("relationships")]
public JsonElement Relationships { get; set; }
}
}
An example call looks like this:-
POST http://localhost:5000/api/test/event
Content-Type: application/vnd.api+json; charset=UTF-8
{
"data": {
"type": "test",
"attributes": {
"source": "postman",
"instance": "jg",
"level": "INFO",
"message": "If this comes back with an ID, the API is probably working"
}
}
}
When I examine the contents of payload at a breakpoint in Visual Studio, it looks OK at the top level but the JsonElement bits look opaque, so I don't know if they've been parsed properly. Their structure can vary, so we only care that they are valid JSON. In the old NewtonSoft version, they were JObjects.
After the GUID has been added, it appears in the payload object when examined at a breakpoint but I'm suspicious that the problem is related to other elements in the object being read-only or something similar.
Your problem can be reproduced with the following more minimal example. Define the following model:
public class JsonApiMessage
{
public JsonElement data { get; set; }
}
Then attempt to deserialize and re-serialize an empty JSON object like so:
var payload = JsonSerializer.Deserialize<JsonApiMessage>("{}");
var newJson = JsonSerializer.Serialize(payload, new JsonSerializerOptions { WriteIndented = true });
And you will get an exception (demo fiddle #1 here):
System.InvalidOperationException: Operation is not valid due to the current state of the object.
at System.Text.Json.JsonElement.WriteTo(Utf8JsonWriter writer)
at System.Text.Json.Serialization.Converters.JsonConverterJsonElement.Write(Utf8JsonWriter writer, JsonElement value, JsonSerializerOptions options)
The problem seems to be that JsonElement is a struct, and the default value for this struct can't be serialized. In fact, simply doing JsonSerializer.Serialize(new JsonElement()); throws the same exception (demo fiddle #2 here). (This contrasts with JObject which is a reference type whose default value is, of course, null.)
So, what are your options? You could make all your JsonElement properties be nullable, and set IgnoreNullValues = true while re-serializing:
public class JsonApiData
{
[JsonPropertyName("type")]
public string Type { get; set; }
[JsonPropertyName("id")]
public string Id { get; set; }
[JsonPropertyName("attributes")]
public JsonElement? Attributes { get; set; }
[JsonPropertyName("meta")]
public JsonElement? Meta { get; set; }
[JsonPropertyName("relationships")]
public JsonElement? Relationships { get; set; }
}
And then:
var reserialisedPayload = JsonSerializer.Serialize(payload, new JsonSerializerOptions { IgnoreNullValues = true });
Demo fiddle #3 here.
Or, in .NET 5 or later, you could mark all of your JsonElement properties with [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]:
public class JsonApiData
{
// Remainder unchanged
[JsonPropertyName("attributes")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
public JsonElement Attributes { get; set; }
[JsonPropertyName("meta")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
public JsonElement Meta { get; set; }
[JsonPropertyName("relationships")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
public JsonElement Relationships { get; set; }
}
Doing so will cause uninitialized elements to be skipped during serialization without needing to modify serialization options.
Demo fiddle #4 here.
Or, you could simplify your data model by binding all the JSON properties other than Id to a JsonExtensionData property like so:
public class JsonApiData
{
[JsonPropertyName("id")]
public string Id { get; set; }
[JsonExtensionData]
public Dictionary<string, JsonElement> ExtensionData { get; set; }
}
This approach avoids the need to manually set IgnoreNullValues when re-serializing, and thus ASP.NET Core will re-serialize the model correctly automatically.
Demo fiddle #5 here.
The exception is right - the state of the object is invalid. The Meta and Relasionships elements are non-nullable but the JSON string doesn't contain them. The deserialized object ends up with Undefined values in those properties that can't be serialized.
[JsonPropertyName("meta")]
public JsonElement? Meta { get; set; }
[JsonPropertyName("relationships")]
public JsonElement? Relationships { get; set; }
The quick fix would be to change those properties to JsonElement?. This will allow correct deserialization and serialization. By default, the missing elements will be emitted as nulls:
"meta": null,
"relationships": null
To ignore them, add the IgnoreNullValues =true option :
var newJson = JsonSerializer.Serialize(payload, new JsonSerializerOptions
{ WriteIndented = true,IgnoreNullValues =true });
The real solution though would be to get rid of all that code. It hampers the use of System.Text.Json. Left by itself, ASP.NET Core uses Pipelines to read the input stream without allocating, deserializes the payload and calls the method with the deserialized object as a parameter, using minimal allocations. Any return values are serialized in the same way.
The question's code though allocates a lot - it caches the input in the StreamReader, then the entire payload is cached in the payloadString and then again, as the payload object. The reverse process also uses temporary strings. This code takes at least twice as much RAM as ASP.NET Core would use.
The action code should be just :
[HttpPost("{eventType}")]
public async Task<IActionResult> ProcessEventAsync([FromRoute] string eventType,
MyApiData payload)
{
Guid messageID = Guid.NewGuid();
payload.Data.Id = messageID.ToString();
return Accepted(payload);
}
Where MyApiData is a strongly-typed object. The shape of the Json example corresponds to :
public class Attributes
{
public string source { get; set; }
public string instance { get; set; }
public string level { get; set; }
public string message { get; set; }
}
public class Data
{
public string type { get; set; }
public Attributes attributes { get; set; }
}
public class MyApiData
{
public Data data { get; set; }
public Data[] included {get;set;}
}
All other checks are performed by ASP.NET Core itself - ASP.NET Core will reject any POST that doesn't have the correct MIME type. It will return a 400 if the request is badly formatted. It will return a 500 if the code throws
I have this object value that is being returned and I would like to convert it into a useful JSON object that I can inspect and manipulate. Ultimately, my goal is to validate the values of username and accessKey. But 2 things are throwing this off. Double {{ makes it invalid JSON and sauce:options can't be converted into a property in a class.
{{
"browserName": "MicrosoftEdge",
"browserVersion": "latest",
"platformName": "Windows 10",
"sauce:options": {
"username": "test",
"accessKey": "123"
}
}}
Here is what I tried:
string output = JsonConvert.SerializeObject(SauceSession.Options.ConfiguredEdgeOptions);
This SauceSession.Options.ConfiguredEdgeOptions returns that object I mentioned above.
Got this back:
Newtonsoft.Json.JsonSerializationException: 'Error getting value from 'BinaryLocation' on 'OpenQA.Selenium.Edge.EdgeOptions'.'
I also tried this as per suggestions:
var serialized = JsonConvert.SerializeObject(SauceSession.Options.ConfiguredEdgeOptions);
And got back this Newtonsoft.Json.JsonSerializationException: 'Error getting value from 'BinaryLocation' on 'OpenQA.Selenium.Edge.EdgeOptions'.'
Since you cannot fix the source, you're going to have to apply a bodge to fix the JSON, for example this will work:
var fixedJson = sourceJson.Substring(1, Json.Length - 2);
Now you should have a couple of classes to hold your data, this way you can also cope with the unusual names:
public class Root
{
public string BrowserName { get; set; }
public string BrowserVersion { get; set; }
public string PlatformName { get; set; }
[JsonProperty("sauce:options")]
public Options SauceOptions { get; set; }
}
public class Options
{
public string Username { get; set; }
public string AccessKey { get; set; }
}
And now you should be able to deserialise like this:
var root = JsonConvert.DeserializeObject<Root>(fixedJson);
I am receiving API responses from 3rd party that have ambiguous types. For some methods it is:
{"error":{"message":"Resource is already part of this app","status_code":400}}
And on other calls it is:
{"error": "Resource is already part of this app" }
Is it possible to deserialize such responses into something like:
public class Response
{
[JsonProperty("error")]
public string Error { get; set; }
[JsonIgnore] //[JsonProperty("error")]
public ObjectError ObjectError { get; set; }
}
public class ObjectError
{
[JsonProperty("message")]
public string Message { get; set; }
[JsonProperty("status_code")]
public string StatusCode { get; set; }
}
UPDATE
So I have ended up using object as catch all for deserialization.
[JsonProperty("error")]
public object Error { get; set; }
public string ErrorAsString => Error is string ? Error.ToString() : null;
public ObjectError ErrorAsObject => Error is string ? null : Error != null ? JsonConvert.DeserializeObject<ObjectError>(Error.ToString()) : null;
It's not ideal, I know.
You can do this easily using NetwosoftJson. Here you can check on how to deserialize into an object using it.
You could always parse the JSON object and check to see if it has specific fields. For example:
JObject json = JObject.Parse(jsonString);
if(json.HasKey("message"))
{
var result = JsonConvert.DeserializeObject<ObjectError>(jsonString);
// Do some stuff here
}
else
{
var result = JsonConvert.DeserializeObject<Response>(jsonString);
// Do some stuff here
}
I am requesting a JSON from a standard web service and I need to handle the response so I can work with the objects. I am working in Xamarin Studio - not that i think that matters.
You can see a result from web service by:
https://dawa.aws.dk/vejnavne/autocomplete?q=due
This is requesting street names in Denmark with 'due' in it.
public async Task doAsyncAddress(string input)
{
var template = "https://dawa.aws.dk/vejnavne/autocomplete?q={0}";
var url = string.Format(template, input);
using (var httpClient = new HttpClient())
{
try
{
Task<HttpResponseMessage> getResponse = httpClient.GetAsync(url);
HttpResponseMessage response = await getResponse;
var responseJsonString = await response.Content.ReadAsStringAsync();
/*
I have tried different things here, with with JsonConvert and JObject but neither works.. I have an idea that the son string is in wrong format, with "\n" included and i have tried to remove these, but still without results. I can see the string so I know it is there.. But it is not formatted correctly.
*/
}
catch (Exception ex)
{
string message = ex.Message;
return message;
}
}
}
With the JsonConverter.DeserializeObject i do this:
var adress = JsonConvert.DeserializeObject<List<Address>>(responseJsonString);
where Address:
public class Address
{
public string tekst { get; set; }
public List<Vejnavne> vejnavn
{ get; set; }
}
public class Vejnavne
{
public string href { get; set; }
public string navn { get; set; }
}
and the response is:
"Cannot deserialize the current JSON object (e.g.
{\"name\":\"value\"}) into type
'System.Collections.Generic.List`1[MinEjendom.Vejnavne]' because the
type requires a JSON array (e.g. [1,2,3]) to deserialize
correctly.\nTo fix this error either change the JSON to a JSON array
(e.g. [1,2,3]) or change the deserialized type so that it is a normal
.NET type (e.g. not a primitive type like integer, not a collection
type like an array or List) that can be deserialized from a JSON
object. JsonObjectAttribute can also be added to the type to force it
to deserialize frenter code hereom a JSON object.\nPath
'[0].vejnavn.href', line 5, position 11.”
And with JObject i get:
"Error reading JObject from JsonReader. Current JsonReader item is not
an object: StartArray. Path '', line 1, position 1."
Your C# code is wrong. This is the correct one:
public class Vejnavn
{
public string href { get; set; }
public string navn { get; set; } // not List<Vejnavne> vejnavn
}
public class Address
{
public string tekst { get; set; }
public Vejnavn vejnavn { get; set; }
}
Then call it like this:
var adress = JsonConvert.DeserializeObject<List<Address>>(responseJsonString);
When you've JSON, you are .NET developer and finally - you have to convert JSON to C# class, you should use Edit - > Paste Special -> Paste JSON as classes. This is an awesome tool :)
Your code is wrong. This is the generated class from your JSON :
public class Class1
{
public string tekst { get; set; }
public Vejnavn vejnavn { get; set; }
}
public class Vejnavn
{
public string href { get; set; }
public string navn { get; set; }
}
When you have successfully generated your code, you can rename the class.