JSON.NET Object Deserialisation with class - c#-4.0

I am using these classes:
public class MasteryPages
{
internal MasteryPages() { }
[JsonProperty("pages")]
public List<MasteryPage> Pages { get; set; }
[JsonProperty("summonerId")]
public long SummonerId { get; set; }
}
[Serializable]
public class MasteryPage
{
internal MasteryPage() { }
[JsonProperty("current")]
public bool Current { get; set; }
[JsonProperty("id")]
public long Id { get; set; }
[JsonProperty("name")]
public string Name { get; set; }
[JsonProperty("talents")]
public List<Talent> Talents { get; set; }
}
[Serializable]
public class Talent
{
internal Talent() { }
[JsonProperty("id")]
public int Id { get; set; }
[JsonProperty("name")]
public string Name { get; set; }
[JsonProperty("rank")]
public int Rank { get; set; }
}
This is the code I'm using to deserialise the object
//MASTERIES
var jsonMasteries = requester.CreateRequest(string.Format(RootUrl, Region) + string.Format(MasteriesUrl, summonerId));
var objAllMasteryPages = JsonConvert.DeserializeObject<MasteryPages>(jsonMasteries);
The jsonMasteries object is correctly serialized and gives me this:
http://pastebin.com/3dkdDHdx (Rather large, to view easily: go to http://www.jsoneditoronline.org/ and paste it)
The second line is giving me troubles however. Normally my object should be filled with the data. It unfortunately isn't and I have no idea what's wrong.
Anyone could help me out?

Your problem is in this part of serialized JSON: "42177333": { ... }
As I understand - this is some kind of ID and it's dynamic.
Possible solutions are:
One of possible resolutions is here: C# deserialize dynamic JSON
Cut this part of dynamic JSON.
Try to modify the serialization stuff to avoid this dynamic ID.

Thanks to sleepwalker I saw what was wrong. (Dynamic Id (number), first line)
Now, the James Newtonking JSON library has a solution for dynamic id's like this.
I edited my code to this:
var jsonMasteries = requester.CreateRequest(string.Format(RootUrl, Region) + string.Format(MasteriesUrl, summonerId));
var objAllMasteriePages = JsonConvert.DeserializeObject<Dictionary<long, MasteryPages>>(jsonMasteries).Values.FirstOrDefault().Pages;
(First line stays the same, the magic is in the second line)
Now, i use a dictionary with the key being my given Id, and my custom class.
This works wonders

Related

Automapper - flattening of object property

let's say I have
public class EFObject
{
public int Id { get; set; }
public int NavId { get; set; }
public NavObject Nav { get; set; }
}
public class DTOObject
{
public int Id { get; set; }
public int NavId { get; set; }
public string NavName { get; set; }
}
My expectation was high, and I thought to my self the built-in flattening should handle this, so my mapping is very simple
CreateMap<DTOObject, EFObject>().ReverseMap();
Unfortunately, converting DTOObject to EFObject does not work as expected because EFObject.Nav is null. Since I used the name NavId and NavName I would expect it to create a new NavObject and set the Nav.Id and Nav.Name accordingly.
My Problem : Is there a feature in Automapper that will allow me to achieve the intended result without having to manually write a rule to create an NavObject when mapping the Nav property?.
Unflattening is only configured for ReverseMap. If you want unflattening, you must configure Entity -> Dto then call ReverseMap to create an unflattening type map configuration from the Dto -> Entity.
as noted by Automapper documentation here

Pass different AutoMapper context per nested mapping

I know we can set the Context items when we call Map(), and it will be available to every map operation. Is there a way to change those context items during mapping?
Suppose I have these source types:
public class OuterSource {
public string TimeZone { get; set; }
public string Name { get; set; }
public InnerSource[] InnerArray { get; set; }
}
public class InnerSource {
public DateTime Created { get; set; }
public string Message { get; set; }
}
and these destination types:
public class OuterDest {
public string Name { get; set; }
public InnerDest[] InnerArray { get; set; }
}
public class InnerDest {
public DateTime Created { get; set; }
public string Message { get; set; }
}
The only difference is that InnerSource.Created is in UTC and I want to map it to the local time zone. However the time zone is in OuterSource, not InnerSource.
Normally, I would set up my mappers like so:
CreateMap<OuterSource, OuterDest>();
CreateMap<InnerSource, InnerDest>();
But that wouldn't work because when it comes to mapping InnerSource to InnerDest it does not have access to OuterSource.TimeZone.
So I'm currently forced to set my mapping like so:
CreateMap<OuterSource, OuterDest>()
.ForMember(dest => dest.InnerArray, opt => opt.ResolveUsing(
//loop through source.InnerArray and do the datetime
//conversion manually
));
I consider that a code smell. What I would love to do is to pass the timezone to the nested mapping somehow. I would appreciate any pointers towards that direction.

ServiceStack - Dynamic/Object in DTO

I am running into an issue while looking at SS.
I am writing a custom Stripe implementation and got stuck on web hooks, this in particular:
https://stripe.com/docs/api#event_object
data->object - this can be anything.
Here is my DTO for it:
public class StripeEvent
{
public string id { get; set; }
public StripeEventData data { get; set; }
public string type { get; set; }
}
[DataContract]
public class StripeEventData
{
[DataMember(Name = "object")]
public object _object { get; set; }
}
My hope is to basically just get that object as a string, and then parse it:
var invoice = (StripeInvoice)JsonSerializer.DeserializeFromString<StripeInvoice>(request.data._object.ToString());
Unfortunately the data that is returned from ToString does not have quotes surrounding each json property's name:
Capture
So, the DeserializeFromString returns an object that has everything nulled out.
Why does SS internally strip the quotes out? Is this the proper way to handle a json member that can be one of many different types? I did try the dynamic stuff, but did not have any luck with that either - basically the same result with missing quotes.
I searched very thoroughly for the use of objects and dynamic within DTOs, but there really was nothing that helped with this question.
Thank you!
The issue is that you should never have an object type in DTOs as the serializer has no idea what concrete type to deserialize back into.
The Stripe documentation says object is a hash which you should be able to use a Dictionary to capture, e.g:
public class StripeEventData
{
public Dictionary<string,string> #object { get; set; }
}
Or as an alternative you could use JsonObject which provides a flexible API to access dynamic data.
This will work for flat object structures, but for complex nested object structures you'll need to create Custom Typed DTOs, e.g:
public class StripeEventInvoice
{
public string id { get; set; }
public StripeEventDataInvoice data { get; set; }
public string type { get; set; }
}
public class StripeEventData
{
public StripeInvoice #object { get; set; }
}

Automapper, mapping single destination property as a concatenation of multiple source property

I have a situation where I need to map a single property as a combination of multiple source properties based on some conditions.
Destination :
public class Email
{
public Email() {
EmailRecipient = new List<EmailRecipient>();
}
public string Subject{get; set;}
public string Body {get; set;}
public virtual ICollection<EmailRecipient> EmailRecipient { get; set; }
}
public class EmailRecipient
{
public int EmaiId { get; set; }
public string RecipientEmailAddress { get; set; }
public int RecipientEmailTypeId { get; set; }
public virtual Email Email { get; set; }
}
Source:
public class EmailViewModel
{
public List<EmailRecipientViewModel> To { get; set; }
public List<EmailRecipientViewModel> Cc { get; set; }
public string Subject { get; set; }
public string Body { get; set; }
}
public class EmailRecipientViewModel
{
public string RecipientEmailAddress { get; set; }
}
I want Mapper.Map<EmailViewModel,Email>()
Here I would like to map my Email.EmailRecipient as a combination of EmailViewModel.To and EmailViewModel.Cc.
However the condition is, Email.EmailRecipient.RecipientEmailTypeId will be 1 for To and 2 for Cc
Hope my question is clear.
One possible way to achieve this is to create a map that uses a specific method for this conversion. The map creation would be:
Mapper.CreateMap<EmailViewModel, Email>()
.ForMember(e => e.EmailRecipient, opt => opt.MapFrom(v => JoinRecipients(v)));
Where the JoinRecipients method would perform the conversion itself. A simple implementation could be something like:
private ICollection<EmailRecipient> JoinRecipients(EmailViewModel viewModel) {
List<EmailRecipient> result = new List<EmailRecipient>();
foreach (var toRecipient in viewModel.To) {
result.Add(new EmailRecipient {
RecipientEmailTypeId = 1,
RecipientEmailAddress = toRecipient.RecipientEmailAddress
});
}
foreach (var ccRecipient in viewModel.Cc) {
result.Add(new EmailRecipient {
RecipientEmailTypeId = 2,
RecipientEmailAddress = ccRecipient.RecipientEmailAddress
});
}
return result;
}
I'm a huge opponent of converters, mostly because for other people in your project, things will just happen 'like magic' after the mapping call.
An easier way of handling this would be to implement the property as a method that converts other properties on the viewmodel to the required formatting. Example:
public class EmailViewModel
{
public ICollection<EmailRecipient> EmailRecipient {
get {
return To.Union(Cc);
}
}
public List<EmailRecipientViewModel> To { get; set; }
public List<EmailRecipientViewModel> Cc { get; set; }
public string Subject { get; set; }
public string Body { get; set; }
}
Now automapper automatically maps from EmailRecipient property to EmailRecipient property, and if someone is trying to figure out how it happens, they just need to look on the viewmodel.
Editing this some years later: Just as a warning, doing things this way means that every time you call EmailRecipient, you incur the o(n) task of unioning the To and Cc fields. This is fine if you're only dealing with one email, but if you're reusing the viewmodel and someone sticks it in a loop with say, every other email in the system, it might be a huge performance issue. In that case I'd go with the accepted answer so that you dodge this potential performance pitfall.

Breeze doesn't expand TPH entities correctly

Breeze doesn't expand TPH entities correctly.
When using expand in breeze if you are using TPH expand will only work for the first entity, the others properties will be null. If I change the entity not to use inheritances it works fine. I've also tested returning each entity separately in an expand query that also worked fine.
//client side code
var getResidentById = function (id, obs) {
var query = EntityQuery.from('Residents')
.where('id', '==', id)
.expand('user, currentUnit, leases, leases.unit, leases.leaseStatus');
return manager.executeQuery(query).then(function (data) {
if (obs) {
obs(data.results[0])
}
}, queryFailed);
};
//Controler Endpoint
[HttpGet]
public IQueryable<Resident>
{
return _context.Context.UserDetails.OfType<Resident>();
}
//Model
public class UserDetail : EntityBase<int>, IArchivable, IHasPhoto, IDeactivatableEntity, IUpdatable
{
public bool IsArchived { get; set; }
public int LastUpdatedById { get; set; }
public UserProfile LastUpdatedBy { get; set; }
public DateTimeOffset LastUpdatedDate { get; set; }
public string PhotoUri { get; set; }
public bool IsInactive { get; set; }
}
public abstract class UserBelongsToApartmentComplex : UserDetail, IBelongsToApartmentComplex
{
public int ApartmentComplexId { get; set; }
public virtual ApartmentComplex ApartmentComplex { get; set; }
public virtual bool IsInSameComplexAs(IRelatedToApartmentComplex otherEntity)
{
return ApartmentComplexId == otherEntity.ApartmentComplexId;
}
}
public class Staff : UserBelongsToApartmentComplex
{
public string Title { get; set; }
}
public class Admin : UserDetail
{
public string AccessLevel { get; set; }
}
public class Resident : UserBelongsToApartmentComplex
{
public string Pets { get; set; }
public bool HasInsurance { get; set; }
public virtual IList<Lease> Leases { get; set; }
public int? CurrentUnitId { get; set; }
public virtual Unit CurrentUnit { get; set; }
public Resident()
{
Leases = new List<Lease>();
}
}
//response data from sever from endpoint public IQueryable Residents()
[{"$id":"1","$type":"RadiusBlue.Core.Models.Resident, RadiusBlue.Core","Pets":"Sadie, a westie","HasInsurance":false,"Leases":[{"$id":"2","$type":"RadiusBlue.Core.Models.Lease, RadiusBlue.Core","Start":"2012-05-23T00:00:00.000","End":"2013-05-23T00:00:00.000","UnitId":2,"Unit":{"$id":"3","$type":"RadiusBlue.Core.Models.Unit, RadiusBlue.Core","Building":"B","Floor":2,"ModelName":"Tera","RentAmount":2500.00,"NumberOfBeds":1,"NumberOfBaths":3,"UnitName":"102A","IsInactive":true,"Inhabitants":[],"ApartmentComplexId":1,"ApartmentComplex":{"$id":"4","$type":"RadiusBlue.Core.Models.ApartmentComplex, RadiusBlue.Core","Name":"The Stratford","StreetAddress":"100 S Park Ave","City":"Winter Park","StateId":10,"ZipCode":"32792","PropertyManagementCompanyId":1,"IsInactive":false,"TimeZoneId":"Eastern Standard Time","TimeZone":{"$id":"5","$type":"System.TimeZoneInfo, mscorlib","Id":"Eastern Standard Time","DisplayName":"(UTC-05:00) Eastern Time (US & Canada)","StandardName":"Eastern Standard Time","DaylightName":"Eastern Daylight Time","BaseUtcOffset":"-PT5H","AdjustmentRules":[{"$id":"6","$type":"System.TimeZoneInfo+AdjustmentRule, mscorlib","DateStart":"0001-01-01T00:00:00.000","DateEnd":"2006-12-31T00:00:00.000","DaylightDelta":"PT1H","DaylightTransitionStart":{"$id":"7","$type":"System.TimeZoneInfo+TransitionTime, mscorlib","TimeOfDay":"0001-01-01T02:00:00.000","Month":4,"Week":1,"Day":1,"DayOfWeek":"Sunday","IsFixedDateRule":false},"DaylightTransitionEnd":{"$id":"8","$type":"System.TimeZoneInfo+TransitionTime, mscorlib","TimeOfDay":"0001-01-01T02:00:00.000","Month":10,"Week":5,"Day":1,"DayOfWeek":"Sunday","IsFixedDateRule":false}},{"$id":"9","$type":"System.TimeZoneInfo+AdjustmentRule, mscorlib","DateStart":"2007-01-01T00:00:00.000","DateEnd":"9999-12-31T00:00:00.000","DaylightDelta":"PT1H","DaylightTransitionStart":{"$id":"10","$type":"System.TimeZoneInfo+TransitionTime, mscorlib","TimeOfDay":"0001-01-01T02:00:00.000","Month":3,"Week":2,"Day":1,"DayOfWeek":"Sunday","IsFixedDateRule":false},"DaylightTransitionEnd":{"$id":"11","$type":"System.TimeZoneInfo+TransitionTime, mscorlib","TimeOfDay":"0001-01-01T02:00:00.000","Month":11,"Week":1,"Day":1,"DayOfWeek":"Sunday","IsFixedDateRule":false}}],"SupportsDaylightSavingTime":true},"Users":[{"$ref":"1"}],"Groups":[],"IsArchived":false,"ApartmentComplexId":1,"Id":1},"Id":2},"ResidentId":3,"Resident":{"$ref":"1"},"LeaseStatusId":4,"LeaseStatus":{"$id":"12","$type":"RadiusBlue.Core.Models.LeaseStatus, RadiusBlue.Core","Description":"Lost","Id":4},"Id":1},{"$id":"13","$type":"RadiusBlue.Core.Models.Lease, RadiusBlue.Core","Start":"2013-05-24T00:00:00.000","End":"2014-05-24T00:00:00.000","UnitId":1,"Unit":{"$id":"14","$type":"RadiusBlue.Core.Models.Unit, RadiusBlue.Core","Building":"A","Floor":2,"ModelName":"Aqua","RentAmount":2000.00,"NumberOfBeds":2,"NumberOfBaths":1,"UnitName":"101A","IsInactive":true,"Inhabitants":[{"$ref":"1"}],"ApartmentComplexId":1,"ApartmentComplex":{"$ref":"4"},"Id":1},"ResidentId":3,"Resident":{"$ref":"1"},"LeaseStatusId":1,"LeaseStatus":{"$id":"15","$type":"RadiusBlue.Core.Models.LeaseStatus, RadiusBlue.Core","Description":"Active","Id":1},"Id":2}],"CurrentUnitId":1,"CurrentUnit":{"$ref":"14"},"ApartmentComplexId":1,"ApartmentComplex":{"$ref":"4"},"Id":3,"User":{"$id":"16","$type":"RadiusBlue.Core.Models.UserProfile, RadiusBlue.Core","UserName":"vjiawon#gmail.com","FirstName":"Vishal","LastName":"Jiawon","Age":27,"PhoneNumber":"123 456 7890","IsInactive":false,"UserDetail":{"$ref":"1"},"GroupMembers":[],"MaintenanceRequests":[],"Id":3},"IsArchived":false,"LastUpdatedById":1,"LastUpdatedDate":"0001-01-01T00:00:00.000+00:00","IsInactive":false,"CreatedById":1,"CreatedDate":"0001-01-01T00:00:00.000+00:00"}]
I do not doubt that there is a bug in BreezeJS somewhere.
I can report that, at least as of v.1.3.4, Breeze can expand multiple navigation properties of a TPH class ... and not just on the first entity returned.
I just modified the "can navigate to AccountType eagerly loaded with expand" test in inheritanceTests.js in DocCode so that (a) it also expands the Status navigation and (b) the tests are performed on the 3rd entity returned rather than the 1st.
The query is something like this:
var em = newEm(); // clean, empty EntityManager
return EntityQuery.from('bankRootTPHs').take(3)
.expand('AccountType, Status'))
.using(em).execute().then(success).fail(handleFail);
...
function success(data) {
var entity = data.results[data.results.length-1]; // get the last one (the 3rd)
var type = data.query.entityType.shortName;
if (!entity) {
ok(false, "a query failed to return a single " + type);
}
// more tests
// I just set a breakpoint and inspected
// entity.accountType() and entity.status()
// Both returned the expected related entities
}
I see that both the related AccountType and the related Status are available from the entity.
So something else is wrong.
Questions about your Example
First I am compelled to observe that you have a lot of expands. I count 5 related entities. That can hurt performance. I know we're not talking about that but I'm calling it out.
Second, the super class UserDetail is concrete but the intermediate derived class UserBelongsToApartmentComplex is abstract. You have inheritance class hierarchies that go concrete/abstract/concrete. The queried type, Residents is one such class. And a class at every level maps to the "UserDetail" table, yes?
I'm pretty sure we didn't test for that scenario ... which is pretty uncommon. I wasn't even sure that worked! For now I have to take your word for it that EF allows such a construct.
It would seem that BreezeJS is confused about it. We'll take a look.

Resources