I am trying to write wrapper for Capsule CRM API using RestSharp.
I have problem with their API service. It returns JSON object when data is present and empty string when object is absent on CRM.
e.g., look on contacts:
{"organisation":{"id":"97377548","contacts":"","pictureURL":"","createdOn":"2016-02-08T14:27:12Z","updatedOn":"2016-02-08T14:27:12Z","lastContactedOn":"2013-12-03T21:00:00Z","name":"some name"}}
{"organisation":{"id":"97377548","contacts":{"email":{"id":"188218414","emailAddress":"someemail"},"phone":{"id":"188218415","type":"Direct","phoneNumber":"phone"}},"pictureURL":"","createdOn":"2016-02-08T14:27:12Z","updatedOn":"2016-02-08T14:27:12Z","lastContactedOn":"2013-12-03T21:00:00Z","name":"some name"}}
To match contacts I have class:
public class Contacts
{
public List<Address> Address { get; set; }
public List<Phone> Phone { get; set; }
public List<Website> Website { get; set; }
public List<Email> Email { get; set; }
}
and property Contacts in class that I am trying to match:
public Contacts Contacts { get; set; }
Everything works fine when API returns JSON object, but I get exception when I get empty string for contacts from API:
Unable to cast object of type 'System.String' to type
'System.Collections.Generic.IDictionary`2[System.String,System.Object]'.
How to avoid this problem?
Is there any way to make conditional matching based on data that is returned from API?
How I can tell RestSharp do not throw exception, just skip property if it can't match?
As you have control over your API, instead of returning "contacts":"" in the response, return "contacts":"{}" and that should avoid your error.
If you cannot alter the response from the API, you'll need to implement a custom serializer, as "" to object isn't supported by RestSharp.
This article summarizes how to use JSON.Net as the serializer, which would enable you to use whatever rules you needed to for your deserialization.
Article summary
First, implement ISerializer and IDeserializer interfaces in a NewtonsoftJsonSerializer class. This would give you full control on how the JSON is desierialized, so you can make "" work for an empty object.
Then, to use it on a request:
private void SetJsonContent(RestRequest request, object obj)
{
request.RequestFormat = DataFormat.Json;
request.JsonSerializer = new NewtonsoftJsonSerializer();
request.AddJsonBody(obj);
}
and to use it on a response:
private RestClient CreateClient(string baseUrl)
{
var client = new RestClient(baseUrl);
// Override with Newtonsoft JSON Handler
client.AddHandler("application/json", new NewtonsoftJsonSerializer());
client.AddHandler("text/json", new NewtonsoftJsonSerializer());
client.AddHandler("text/x-json", new NewtonsoftJsonSerializer());
client.AddHandler("text/javascript", new NewtonsoftJsonSerializer());
client.AddHandler("*+json", new NewtonsoftJsonSerializer());
return client;
}
Related
Basically, I have this class in my C# project:
public class Item
{
[BsonId]
public ObjectId Id { get; set; }
public string AppId { get; set; }
public JsonObject Fields { get; set; }
}
And I've created a couple of items in a Mongo DB using a POST request with this body:
{
"AppId":"(an already existing app id)",
"Fields":
{
"NombreCliente": "Pepillo",
"Email": "pepiko#email.com",
"Telefono": "pp56656784",
"Localidad": "Pepeland",
"Facturas": ["848435498","0564864984"]
}
}
And they were correctly created.
Problem occurs whenever I try to get these items. The error in the tittle pops up:
"(An error occurred while deserializing the Fields property of class divitiae_api.Models.Item: Cannot dynamically create an instance of type 'System.Text.Json.Nodes.JsonObject'. Reason: No parameterless constructor defined.)"
What should I do? I'm really lost here... I've tried creating a parameterless constructor but still keeps failing...
UPDATE
To create an Item I'm using the next method. I can't use a direct "Item" because it throws an exception regarding circular reference in the Field property, so I serialize it to a JSON string and then insert it like that into the collection:
public async Task InsertItem(JsonObject item)
{
var options = new JsonSerializerOptions
{
ReferenceHandler = ReferenceHandler.Preserve
};
var jsonString = System.Text.Json.JsonSerializer.Serialize(item, options);
var document = BsonSerializer.Deserialize<BsonDocument>(jsonString);
var collection2 = _repository.db.GetCollection<BsonDocument>("Items");
await collection2.InsertOneAsync(document);
}
And this is the one I'm using to GET them:
public async Task<List<Item>> GetAllAppItems(string appId)
{
var filter = Builders<Item>
.Filter
.Eq(s => s.AppId, appId);
return await Collection.FindAsync(new BsonDocument()).Result.ToListAsync();
}
Think that MongoDB .NET Driver is unable to know how to map an object to JsonObject as JsonObject is not under its library.
An easy fix is to modify the Fields data type to either object or dynamic.
public class Item
{
...
public dynamic Fields { get; set; }
}
I have a situation where I have a core webApi endpoint. I would like to be able to serialize the incoming Json to a Dto. The Dto would contain the necessary fields, but the incoming request may contain additional properties (different for some clients). I need to be able to capture the additional properties as well, but they will only be known at runtime (the records are to be stored in a DocumentDB (Mongo)). I was deserializing to a dynamic object:
[Route("api/Chapter/CreateNewChapter")]
[HttpPost]
public IActionResult CreateNewChapter([FromBody]dynamic incomingJson)
{
dynamic incomingObject = JsonConvert.DeserializeObject<ExpandoObject>(incomingJson.ToString(), new ExpandoObjectConverter());
if (!IsAuthenticated(incomingObject))
return Unauthorized();
var createNewChapter = new CreateNewChapter();
var outgoingJson = createNewChapter.Process(incomingObject);
var resultJson = JsonConvert.SerializeObject(outgoingJson, Formatting.Indented);
return Ok(resultJson);
}
This worked just fine, the problem is that there is no schema or concrete object to use for Swagger, as well as validation on all of the fields was a nightmare.
So ultimately I would like to do something like this:
public class ChapterDto
{
public int ChapterId {get; set};
public string ChapterName {get; set};
)
Then if there are additional properties sent in the request (Json), The properties can be added dynamically at runtime. In addition, I would be adding metadata properties "ParentChapterId" etc.
Should I try to map the incoming json to the dto so I know we have the valid incoming properties, then if that passes map the entire Json object to the dynamic object like I'm doing above? Or is there a better way to achieve this?
You can use JSON extension data. When deserializing, it will map additional properties not found in the DTO to a suitably-attributed dictionary property that you add to the DTO.
Example usage:
public class ChapterDto
{
public int ChapterId { get; set; }
public string ChapterName { get; set; }
[JsonExtensionData]
public IDictionary<string, JToken> AdditionalData { get; set; }
}
When I use azure table storage Insert Entity api to insert data.
There are some types like dateTime,int in my entity class. And as per this doc: By default a property is created as type String, unless you specify a different type.
So I wonder which is the best way to add the odata type to the json payload? The generated json payload with odata type added should like below:
And my current solution is that after the json string is generated, I treat it as string, and add the odata type directly into the json. But I think there should be more better way for this.
My code like below:
Entity class:
public class MyClass
{
public string PartitionKey { get; set; }
public string RowKey { get; set; }
public string Name { get; set; }
public Guid TestId { get; set; }
}
The main method:
static void Main(string[] args)
{
string url_with_sasToken = "https://xxx.table.core.windows.net/ttt?sasToken";
MyClass entity = new MyClass
{
PartitionKey = "ivan2",
RowKey = "y1",
Name = "jack60",
TestId = Guid.NewGuid()
};
//here, I add the odata type for Guid type
string s = JsonConvert.SerializeObject(entity).Replace("}","");
s += ",\"TestId#odata.type\"" + ":" + "\"Edm.Guid\"}";
Console.WriteLine(s);
var content = new StringContent(s, Encoding.UTF8, "application/json");
using (HttpClient client = new HttpClient())
{
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new
MediaTypeWithQualityHeaderValue("application/json"));
var t2 = client.PostAsync(url_with_sasToken , content).GetAwaiter().GetResult();
Console.WriteLine(t2.StatusCode);
}
}
IMHO, a better way to do this is to make use of Reflection and dynamically create JSON by iterating over public properties of a class. You can pick only those properties types of which are supported by Table service (e.g. Guid, DateTime etc.)
That way you're not constantly changing the serialization code to reflect changes in your class definition. In any case using + sign for string concatenation is a bad idea :).
For reference, you can take a look at the code for TableEntity class in Storage SDK. I downloaded the code for release 9.3.2 from Github (https://github.com/Azure/azure-storage-net/releases/tag/v9.3.2) as this was the last version that supported Table in the SDK.
The scenario is something like this:
I have this web-api which will handle a variety of payment gateways, and I want to have the same endpoint for all of those.
So, what I have in mind is to get some json data like this:
{
"OperationId":"N0004",
"Generic_Object_That_Will_Change_According_ToThe_GateWay":
{
"Sale_id":1000,
"CodUser":"1000040",
"Email":"teste#teste.com"
}
}
Or this, for some other payment gateway
{
"OperationId":"N044444",
"Generic_Object_That_Will_Change_According_ToThe_GateWay":
{
"Token":1000,
"UserSettings":{
id: "4563345",
name: "Average Joe"
}
}
}
What I want to do is to transform this "Generic_Object_That_Will_Change_According_ToThe_GateWay" in the specific object for each payment gateway (paypal, or some other), becase each one is completely different, but I don't want that to affect the way the client will call this API - I want it to be as flexible as possible, in a way that you just have to pass whatever data in this Generic_Object_That_Will_Change_According_ToThe_GateWay, and I will then cast it to the proper object and then call another endpoint(like an aggragate microservice design) passing this newly created object.
My idea so far, was creating some class with a generic property like this
public class Payment<Gateway>
{
public int OperationId{ get; set; }
public Gateway paymentGateWay{ get; set; }
}
And this property paymentGateWay could be typed according the available payment Gateways.
And then maybe I could get this data in the API method as Object, and do the necessary casts
[Route("api/payment")]
[HttpPost]
public string Compra(Object payment) {
But, to be honest, I don't know if I'm in the right way.
I already know that I can't have a generic method in a web-api endpoint - so what would be the correct way to get this data in my endpoint considering that a part of this json data is flexible/generic and may be cast to a few different objects.
To summarize, I want to handle json data that can be deserialized to a few different known objects, but I don't want to have a different method in my API to handle each one this possible data scenarios.
if you want a generic method in webapi you have to use JObject
something like the following
public void Post([FromBody] JObject testJObject)
{
//here you have to do some additional work in order to parse and get it working for generic entity
}
in addition to this, you can use the Schema validator against any received request and use the factory pattern in order to create the correct object
here an example
var json =
" {\"OperationId\":\"N0004\",\"Generic_Object_That_Will_Change_According_ToThe_GateWay\":{\"Sale_id\":1000,\"CodUser\":\"1000040\"}}";
JsonSchema paypalschema = new JsonSchema();
paypalschema.Type = JsonSchemaType.Object;
paypalschema.Properties = new Dictionary<string, JsonSchema>
{
{"OperationId", new JsonSchema {Type = JsonSchemaType.String}},
{
"Generic_Object_That_Will_Change_According_ToThe_GateWay",
new JsonSchema {Type = JsonSchemaType.Object,Properties = new Dictionary<string, JsonSchema>
{
{"Sale_id", new JsonSchema {Type = JsonSchemaType.Integer}},
{"CodUser", new JsonSchema {Type = JsonSchemaType.String}},
}}
}
};
JObject requestObject = JObject.Parse( json);
bool valid = requestObject.IsValid(paypalschema);
if (valid)
{
//create your GatewayObject here
}
//else check another gateway object
Consider using JObject or String as your input (And then converting to JObject.) Then you can do some type or data checking before casting. Here's an example shows how they use a pre-defined 'type' value provided, but in lieu of that, you can instead look in the JObject for the 'parts' of each unique provider's payload to determine which type to use.
You can have a generic controller to implement the method and instance-controllers, which inherit of the generic controller:
// I'll rename Gateway to TGateway according to the fact, that it is a generic Type parameter.
public class Payment<TGateway>
{
public int OperationId{ get; set; }
public TGateway paymentGateWay{ get; set; }
}
GenericController:
// Don't add a RouteAttribute to this Controller.
public class GenericController<TGateway>: ApiController
{
// The implementation of the method. No RouteAttribute.
[HttpPost]
public string Compra(Payment<TGateway> payment) {...}
}
InstanceController:
// No need to override the method. RouteAttribute.
[Route("api/payment/"+typeof(AGateway).Name)]
public class AGatewayController : GenericController<AGateway>
{}
EDIT : the moment I asked the question I thougt of trying something..
I've set XmlNamespace property on the request and that did the trick..
request.XmlNamespace = "http://musicbrainz.org/ns/mmd-2.0#";
But I don't really understand as to why...
Next problem is getting RestSharp to recognize xml atributes as object properties
I've been going over this for the most of the weekend and I just don't get it to work.
I'm trying to write a wrapper round a RestFul webservice (MusicBrainz). I'm testing with a simple example : get details of one artist and put it in a custom Artist object.
When I do a Execute on the RestClient it ends ok but my object properties are null..
But when I test the deserialization with the XmlDeserializer the objectproperties are filled (But not for properties that correspond to an attribute, but I'll tackle that later)
What happens between deserialization of the response and putting the object in response.data ?
Quite possible it is a "newbie" error I'm making as this are my first steps with RestSharp..
Help would be much appreciated..
Returnded xml :
<metadata>
<artist type="Group" id="f1548c5b-329e-4036-921c-02213a04b525">
<name>Uriah Heep</name>
<sort-name>Uriah Heep</sort-name>
<country>GB</country>
<life-span>
<begin>1970</begin>
</life-span>
</artist>
</metadata>
My class :
public class Artist
{
public int Id { get; set; }
public string Type { get; set; }
public string Name { get; set; }
public string SortName { get; set; }
public string Country { get; set; }
}
In the following code output properties are filled
var output = xml.Deserialize<Artist>(response);
But the same response does not fill properties when calling
var response = client.Execute<T>(request);
Complete code (I've put the test code in the generic method for sake of simplicity) :
public T Execute<T>(RestRequest request) where T : new()
{
var client = new RestClient();
client.BaseUrl = BaseUrl;
client.Authenticator = null;
//does not fill properties
var response = client.Execute<T>(request);
if (response.ErrorException != null)
{
throw response.ErrorException;
}
var xml = new XmlDeserializer();
//fills properties
var output = xml.Deserialize<Artist>(response);
return response.Data;
}
This happens because Execute method, after receiving response, tries to negotiate it based on the request's RootElement and XmlNamespace properties and copies them to the XmlDeserializer.
Here's a code from RestClient:
handler.RootElement = request.RootElement;
handler.DateFormat = request.DateFormat;
handler.Namespace = request.XmlNamespace;
response.Data = handler.Deserialize<T>(raw);
If you pass a RestRequest with a mismatching XmlNamespace, RestSharp's XmlDeserializer (that uses XDocument behind the scenes) won't be able to map response XML to an object properties and you will get default/null values instead.
Now for default implementation (when you create XmlDeserializer manually), if you don't set a XmlNamespace, deserializer will do an auto-detection that basically ignores all namespaces in the response and maps all properties only by their names.
See source code from XmlDeserializer:
// autodetect xml namespace
if (!Namespace.HasValue())
{
RemoveNamespace(doc);
}
Taking all above into account, it's clear why it started working after you explicitly set XmlNamespace property to a matching namespace in your request object with this line:
request.XmlNamespace = "http://musicbrainz.org/ns/mmd-2.0#";
Execute method copied namespace into deserializer and it mapped XML to object appropriately.