I added jobs like this: but why I get childJob duplicated? - c#

public class Job
{
public long Id { get; set; }
public long? JobId { get; set; }
public Job ParentJob { get; set; }
public ICollection<Job> ChildJobs { get; set; }
public string Name { get; set; }
}
var child = new Job { Name = "ChildJob" };
var parent = new Job { Name = "ParentJob", ChildJobs = new List<Job>()};
parent.ChildJobs.Add(child);
_context.Jobs.Add(parent);
_context.SaveChanges();
I added jobs like this: but why I get childJob duplicated?
[ { "id": 1, "jobId": null, "parentJob": null, "childJobs": [ { "id": 2, "jobId": 1, "childJobs": null, "name": "ChildJob" } ], "name": "ParentJob" }, { "id": 2, "jobId": 1, "parentJob": { "id": 1, "jobId": null, "parentJob": null, "childJobs": [], "name": "ParentJob" }, "childJobs": null, "name": "ChildJob" } ]

Related

Why is Entity Framework behaving as expected in a MVC app and totally diferent in a WebAPI?

I've been working with Entity Framework 6 for some time now and today I started with a new Web API project and as soon as I execute a query that includes some other entity I receive an error like this:
System.Text.Json.JsonException: A possible object cycle was detected. This can either be due to a cycle or if the object depth is larger than the maximum allowed depth of 32.
The thing and do not understand is that, when I used the same Eager Loading while running a regular MVC App it will bring me exactly what I need, nothing else.
So far this is the relevant code I'm working with:
Beer.cs
public class Beer
{
public int Id { get; set; }
[Required, StringLength(100)]
public string Name { get; set; } = null!;
public int BrandId { get; set; }
public virtual Brand Brand { get; set; } = null!;
}
Brand.cs
public class Brand
{
public int Id { get; set; }
[Required, StringLength(100)]
public string Name { get; set; } = null!;
public virtual ICollection<Beer> Beers { get; set;} = null!;
}
BeerRepository.cs
public class BeerRepository : IBeerRepository
{
private readonly BeerContext _context;
public BeerRepository(BeerContext context)
{
_context = context;
}
public async Task CreateBeerAsync(Beer beer)
{
if(beer == null)
throw new ArgumentNullException(nameof(beer));
await _context.Beers.AddAsync(beer);
}
//BUG: This is the one causing me issues!
public async Task<ICollection<Beer>> GetAsync() => await _context.Beers.Include(b => b.Brand).ToListAsync();
public async Task<Beer> GetByIdAsync(int id) => await _context.Beers.FirstAsync(b => b.Id == id);
public async Task SavaChangesAsync()
{
await _context.SaveChangesAsync();
}
}
BeerContext.cs
#nullable disable
public partial class BeerContext : DbContext
{
public DbSet<Beer> Beers { get; set; }
public DbSet<Brand> Brands { get; set; }
public BeerContext(DbContextOptions opt) : base(opt)
{
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
//BEER ENTITY
modelBuilder.Entity<Beer>(entity => {
entity.HasData(
new Beer { Id = 1, BrandId = 1, Name = "Torobayo" },
new Beer { Id = 2, BrandId = 1, Name = "Bock" },
new Beer { Id = 3, BrandId = 1, Name = "Lager Sin Filtrar" },
new Beer { Id = 4, BrandId = 2, Name = "Super Dry" },
new Beer { Id = 5, BrandId = 2, Name = "Soukai" }
);
});
//BRAND ENTITY
modelBuilder.Entity<Brand>(entity => {
entity.HasData(
new Brand{ Id = 1, Name = "Kunstman" },
new Brand{ Id = 2, Name = "Asahi" }
);
});
OnModelCreatingPartial(modelBuilder);
}
partial void OnModelCreatingPartial(ModelBuilder modelBuilder);
}
BeersController.cs
[Route("api/[controller]")]
[ApiController]
public class BeersController : ControllerBase
{
private readonly IBeerRepository _repository;
private readonly IMapper _mapper;
public BeersController(IMapper mapper, IBeerRepository repository)
{
_mapper = mapper;
_repository = repository;
}
[HttpGet]
public async Task<ActionResult<IEnumerable<BeerReadDTO>>> GetAllBeers()
{
var beers = await _repository.GetAsync();
return Ok(_mapper.Map<IEnumerable<BeerReadDTO>>(beers));
}
}
I also changed the Program.cs file like this:
builder.Services.AddControllers().AddJsonOptions(opt => {
opt.JsonSerializerOptions.ReferenceHandler = ReferenceHandler.IgnoreCycles;
});
It works (at least it will remove the error) but instead I'm getting this "nice" data!
[
{
"id": 1,
"name": "Torobayo",
"brand": {
"id": 1,
"name": "Kunstman",
"beers": [
{
"id": 1,
"name": "Torobayo",
"brandId": 1,
"brand": null
},
{
"id": 2,
"name": "Bock",
"brandId": 1,
"brand": null
},
{
"id": 3,
"name": "Lager Sin Filtrar",
"brandId": 1,
"brand": null
}
]
}
},
{
"id": 2,
"name": "Bock",
"brand": {
"id": 1,
"name": "Kunstman",
"beers": [
{
"id": 1,
"name": "Torobayo",
"brandId": 1,
"brand": null
},
{
"id": 2,
"name": "Bock",
"brandId": 1,
"brand": null
},
{
"id": 3,
"name": "Lager Sin Filtrar",
"brandId": 1,
"brand": null
}
]
}
},
{
"id": 3,
"name": "Lager Sin Filtrar",
"brand": {
"id": 1,
"name": "Kunstman",
"beers": [
{
"id": 1,
"name": "Torobayo",
"brandId": 1,
"brand": null
},
{
"id": 2,
"name": "Bock",
"brandId": 1,
"brand": null
},
{
"id": 3,
"name": "Lager Sin Filtrar",
"brandId": 1,
"brand": null
}
]
}
},
{
"id": 4,
"name": "Super Dry",
"brand": {
"id": 2,
"name": "Asahi",
"beers": [
{
"id": 4,
"name": "Super Dry",
"brandId": 2,
"brand": null
},
{
"id": 5,
"name": "Soukai",
"brandId": 2,
"brand": null
}
]
}
},
{
"id": 5,
"name": "Soukai",
"brand": {
"id": 2,
"name": "Asahi",
"beers": [
{
"id": 4,
"name": "Super Dry",
"brandId": 2,
"brand": null
},
{
"id": 5,
"name": "Soukai",
"brandId": 2,
"brand": null
}
]
}
}
]
This is the query running in the background...
info: Microsoft.EntityFrameworkCore.Database.Command[20101]
Executed DbCommand (49ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
SELECT [b].[Id], [b].[BrandId], [b].[Name], [b0].[Id], [b0].[Name]
FROM [Beers] AS [b]
INNER JOIN [Brands] AS [b0] ON [b].[BrandId] = [b0].[Id]
And what I actually need is this:
[
{
"id": 1,
"name": "Torobayo",
"brand":"Kunstman"
},
{
"id": 2,
"name": "Bock",
"brand":"Kunstman"
},
{
"id": 3,
"name": "Lager Sin Filtrar",
"brand":"Kunstman"
},
{
"id": 4,
"name": "Super Dry",
"brand":"Asahi"
},
]
Any help will be a good start since I ran out of ideas. Thank you very much!
EDIT:
So, i decided to also post my DTO File and also the Automapper configuration... looks like it is not clear that i'm actually mapping from Beer entity to the corresponding DTO.
BeerReadDTO.cs
public class BeerReadDTO
{
public int Id { get; set; }
public string Name { get; set; }
public Brand Brand { get; set; }
}
AutoMapperConf.cs
public class AutoMapperConf : Profile
{
public AutoMapperConf()
{
// Source -> Target
CreateMap<Beer,BeerReadDTO>();
}
}
Okay.. thanks to Kamran Khan who pointed me in the right direction!... the issue was fixed just by removing the navigation property from Brands to Beers and changing the Brands for the DTO to a string.
Here are the files i changed and it worked fine!
public class Brand
{
[Key]
public int Id { get; set; }
[Required, StringLength(100)]
public string Name { get; set; } = null!;
//public virtual ICollection<Beer> Beers { get; set;} = null!;
}
public class BeerReadDTO
{
public int Id { get; set; }
public string Name { get; set; }
public string Brand { get; set; }
}
public class AutoMapperConf : Profile
{
public AutoMapperConf()
{
// Source -> Target
CreateMap<Beer,BeerReadDTO>()
.ForMember(d => d.Brand, opt => opt.MapFrom(src => src.Brand.Name));
}
}
Thank you very much!

EF Core Customize JSON Serialization with LINQ

I am building out a demo for an API in .NET Core and constructing a nested JSON object that needs to get constructed through a series of LINQ queries.
My current issue is that when i get about 4 layers deep, I want to customize what actually gets serialized, more specifically, i want to "Not Include" specific navigation properties for this query specifically, but not for general purpose, only for this specific query.
My first thought was to do a DTO, but that seems like an unnecessary extra model just for this one specific case...i'd like to work with LINQ directly to manipulate for my result.
I have already added the following to my Startup.cs file to avoid cycles:
services.AddControllers().AddNewtonsoftJson(options =>
options.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore
);
I have 5 models currently in action that look like this:
public class Hotel
{
public int ID { get; set; }
public string Name { get; set; }
public string StreetAddress { get; set; }
public string City { get; set; }
public string State { get; set; }
public string Phone { get; set; }
public List<HotelRooms> HotelRooms { get; set; }
}
public class Room
{
public int ID { get; set; }
public string Name { get; set; }
public Layout Layout { get; set; }
public List<RoomAmenities> RoomAmenities { get; set; }
public List<HotelRooms> HotelRooms { get; set; }
}
public class HotelRooms
{
public int HotelID { get; set; }
public int RoomNumber { get; set; }
public int RoomID { get; set; }
public decimal Rate { get; set; }
public bool PetFriendly { get; set; }
public Hotel Hotel { get; set; }
public Room Room { get; set; }
}
public class Amenities
{
public int ID { get; set; }
public string Name { get; set; }
// Navigation Properties
public List<RoomAmenities> RoomAmenities { get; set; }
}
public class RoomAmenities
{
public int RoomID { get; set; }
public int AmenitiesID { get; set; }
public Room Room { get; set; }
public Amenities Amenity { get; set; }
}
In my services, i have this logic that is actually doing the querying:
public async Task<Hotel> GetById(int id)
{
var hotel = await _context.Hotel.FindAsync(id);
// Get a list of rooms
var rooms = await _context.HotelRooms.Where(r => r.HotelID == id)
.Include(d => d.Room)
.ThenInclude(a => a.RoomAmenities)
.ThenInclude(x => x.Amenity).ToListAsync();
hotel.HotelRooms = rooms;
return hotel;
}
The current output is:
{
"id": 1,
"name": "Amanda's Hotel",
"streetAddress": "123 CandyCane Lane",
"city": "Seattle",
"state": "WA",
"phone": "123-456-8798",
"hotelRooms": [
{
"hotelID": 1,
"roomNumber": 101,
"roomID": 2,
"rate": 75.00,
"petFriendly": false,
"room": {
"id": 2,
"name": "Queen Suite",
"layout": 2,
"roomAmenities": [
{
"roomID": 2,
"amenitiesID": 1,
"amenity": {
"id": 1,
"name": "Coffee Maker",
"roomAmenities": [
{
"roomID": 1,
"amenitiesID": 1,
"room": {
"id": 1,
"name": "Princess Suite",
"layout": 1,
"roomAmenities": [
{
"roomID": 1,
"amenitiesID": 2,
"amenity": {
"id": 2,
"name": "Mini Bar",
"roomAmenities": []
}
}
],
"hotelRooms": [
{
"hotelID": 1,
"roomNumber": 123,
"roomID": 1,
"rate": 120.00,
"petFriendly": true
}
]
}
}
]
}
}
],
"hotelRooms": []
}
},
{
"hotelID": 1,
"roomNumber": 123,
"roomID": 1,
"rate": 120.00,
"petFriendly": true,
"room": {
"id": 1,
"name": "Princess Suite",
"layout": 1,
"roomAmenities": [
{
"roomID": 1,
"amenitiesID": 1,
"amenity": {
"id": 1,
"name": "Coffee Maker",
"roomAmenities": [
{
"roomID": 2,
"amenitiesID": 1,
"room": {
"id": 2,
"name": "Queen Suite",
"layout": 2,
"roomAmenities": [],
"hotelRooms": [
{
"hotelID": 1,
"roomNumber": 101,
"roomID": 2,
"rate": 75.00,
"petFriendly": false
}
]
}
}
]
}
},
{
"roomID": 1,
"amenitiesID": 2,
"amenity": {
"id": 2,
"name": "Mini Bar",
"roomAmenities": []
}
}
],
"hotelRooms": []
}
}
]
}
This is much too nested for me, i'd like the inner "roomAmenities" inside the "amenity" object to not be included. I'd like my object to look like this:
{
"id": 1,
"name": "Amanda's Hotel",
"streetAddress": "123 CandyCane Lane",
"city": "Seattle",
"state": "WA",
"phone": "123-456-8798",
"hotelRooms": [
{
"hotelID": 1,
"roomNumber": 101,
"roomID": 2,
"rate": 75,
"petFriendly": false,
"room": {
"id": 2,
"name": "Queen Suite",
"layout": 2,
"roomAmenities": [
{
"roomID": 2,
"amenitiesID": 1,
"amenity": {
"id": 1,
"name": "Coffee Maker"
}
}
],
"hotelRooms": []
}
},
{
"hotelID": 1,
"roomNumber": 123,
"roomID": 1,
"rate": 120,
"petFriendly": true,
"room": {
"id": 1,
"name": "Princess Suite",
"layout": 1,
"roomAmenities": [
{
"roomID": 1,
"amenitiesID": 1,
"amenity": {
"id": 1,
"name": "Coffee Maker"
}
},
{
"roomID": 1,
"amenitiesID": 2,
"amenity": {
"id": 2,
"name": "Mini Bar"
}
}
],
"hotelRooms": []
}
}
]
}
Does anyone have any guidance on how i can achieve this with EFCore and LINQ?
you can check this post
What is the difference between PreserveReferencesHandling and ReferenceLoopHandling in Json.Net?
I see that you have already implemented referenceloopHandling, but even when you are using that you can only make the inner reference as null but not totally remove the key- value pair from json

c# how to count the amount of json values

So, I am trying to count the amount of values in JSON using c#. The Json is:
{
"Someid": 657442,
"roles": [
{
"id": 3892751,
"name": "Guest",
"rank": 0,
"memberCount": 0
},
{
"id": 3892750,
"name": "Fanz!<3",
"rank": 1,
"memberCount": 0
},
{
"id": 3892749,
"name": "Lead-Singer",
"rank": 254,
"memberCount": 0
},
{
"id": 3892748,
"name": "Drums",
"rank": 255,
"memberCount": 0
}
]
}
I want to count the amount "roles". The JSON is just in a string variable. Help?
You can either use like this:
var token = JToken.Parse(input);
var roles= token.Value<JArray>("roles");
var count = roles.Count;
Or you can also use JsonPath:
var token = JToken.Parse(input);
var count = token.SelectTokens("$.roles[*]").Count();
But ideally, you should be serilizing into an object and then using the properties to get the Count:
public class Role
{
public int id { get; set; }
public string name { get; set; }
public int rank { get; set; }
public int memberCount { get; set; }
}
public class MyObject
{
public int Someid { get; set; }
public List<Role> roles { get; set; }
}
var item = JsonConvert.DeserializeObject<MyObject>(input);
var count = item.roles.Count;

Web Api; Entity Framework; Data returns recursive

im facing a problem with probably selfreference Looping:
Model:
public class ProtectedAccount
{
public int Id { get; set; }
[DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public DateTime Created { get; private set; }
public DateTime? Changed { get; set; }
public bool Disabled { get; set; }
public virtual ICollection<ProtectedAccountMember> Members { get; set; }
public virtual ProtectedAccountType Type { get; set; }
}
public class ProtectedAccountMember
{
public int Id { get; set; }
[StringLength(300)]
public string Name { get; set; }
[Index]
public virtual ProtectedAccount ProtectedAccount { get; set; }
}
Controller:
[ResponseType(typeof(ProtectedAccount))]
[Route("ProtectedAccounts/{id}/Members")]
[HttpGet]
public IHttpActionResult GetProtectedAccountMembers(int id)
{
var protectedAccount = db.ProtectedAccounts.Find(id);
if (protectedAccount == null)
{
return NotFound();
}
return Ok(protectedAccount.Members.ToList());
}
the data wich i receive for an GET seems to Loop recursive through all navigations:
[
{
"ProtectedAccount": {
"Members": [
{
"Id": 2,
"Name": "XXX, XX",
},
{
"Id": 3,
"Name": "XX, XX",
}
],
"Type": null,
"Id": 25,
"ObjectGUID": "76bf65e7-af60-4fe8-b3e1-90afbfd65b65",
"Name": "XXX",
},
"Id": 1,
"Name": "test",
},
{
"ProtectedAccount": {
"Members": [
{
"Id": 1,
"Name": "test",
},
{
"Id": 3,
"Name": "XX, XX",
"SamAccountName": "XX",
"Disabled": false
}
],
"Type": null,
"Id": 25,
"ObjectGUID": "76bf65e7-af60-4fe8-b3e1-90afbfd65b65",
"Name": "XXXX",
},
"Id": 2,
"Name": "XX, XX",
},
{
"ProtectedAccount": {a
"Members": [
{
"Id": 1,
"Name": "test",
"SamAccountName": "XXX",
"Disabled": false
},
{
"Id": 2,
"Name": "XX, XX",
"SamAccountName": "XX",
"Disabled": false
}
],
"Type": null,
"Id": 25,
"ObjectGUID": "76bf65e7-af60-4fe8-b3e1-90afbfd65b65",
"Name": "XXX",
},
"Id": 3,
"Name": "XX, XX",
}
]
There is only one "ProtectedAccount" in the database. DO i have to use DTO to overcome this issue? I tried some configuration via the json formatsettings but didnt get any better results.
From your code, you are only returing the protectedAccount.Members, hence you could do a projection query as below
var results = ctx.ProtectedAccountMembers
.Where(member => member.ProtectedAccount.Id == protectedAccount.Id)
.Select(member => new { member.Id, member.Name }).ToList();

Deserialise JSON with unknown fields

I am attempting to deserialise results from the OctoPart API (http://octopart.com/api/docs/v3/rest-api) using c# and json.net
There are no issues deserialising the majority of the data, however I am running troubles with the Part.specs attributes (http://octopart.com/api/docs/v3/rest-api#notes-part.specs) because the attributes change depending on the item returned.
Here is what the API says about Part.Specs
The specs attribute attached to Part instances is a JSON object mapping attribute short names (e.g. "voltage_rating_dc") to QualitativeValue and QuantitativeValue instances.
It's important to note that all value properties of (Qual|Quant)itativeValue objects are JSON arrays. The reason for this is to accomodate multi-valued attributes such as power supplies with multiple output voltages:
Because I want to maintain the class, I believe that I might need to implement a custom JConverter?
I was looking at this question, but wasnt too sure how to apply to this example beccause the attributes can be deserialised into the QualitativeValue / QuantitativeValue classes
Here is my Part class
public class Part
{
public string __class__ { get; set; }
public string uid { get; set; }
public long uid_v2 { get; set; }
public string mpn { get; set; }
public Manufacturer manufacturer { get; set; }
public Brand brand { get; set; }
public string octopart_url { get; set; }
public List<PartOffer> offers { get; set; }
public List<Datasheet> datasheets { get; set; }
public List<ComplianceDocument> compliance_documents { get; set; }
public List<Description> descriptions { get; set; }
public List<ImageSet> imagesets { get; set; }
public Dictionary<string, string> specs { get; set; }
public List<string> category_uids { get; set; }
public List<ExternalLinks> external_links { get; set; }
}
Here is an example result (PartsMatchResponse) from the API
{
"__class__": "PartsMatchResponse",
"msec": 183,
"request": {
"__class__": "PartsMatchRequest",
"exact_only": false,
"queries": [
{
"__class__": "PartsMatchQuery",
"brand": null,
"limit": 10,
"mpn": "ERJ8BWFR010V",
"mpn_or_sku": null,
"q": "",
"reference": null,
"seller": null,
"sku": null,
"start": 0
}
]
},
"results": [
{
"__class__": "PartsMatchResult",
"error": null,
"hits": 1,
"items": [
{
"__class__": "Part",
"brand": {
"__class__": "Brand",
"name": "Panasonic - ECG",
"uid": "4c528d5878c09b95"
},
"category_uids": [
"7542b8484461ae85",
"cd01000bfc2916c6",
"5c6a91606d4187ad"
],
"compliance_documents": [],
"datasheets": null,
"external_links": {
"__class__": "ExternalLinks",
"evalkit_url": null,
"freesample_url": null,
"product_url": null
},
"imagesets": null,
"manufacturer": {
"__class__": "Manufacturer",
"name": "Panasonic - ECG",
"uid": "c20a0700af7c11cd"
},
"mpn": "ERJ8BWFR010V",
"octopart_url": "http://octopart.com/erj8bwfr010v-panasonic+-+ecg-7979066",
"offers": null,
"specs": {
"case_package": {
"__class__": "QualitativeValue",
"attribution": {
"__class__": "Attribution",
"first_acquired": null,
"sources": []
},
"value": [
"1206"
]
},
"case_package_si": {
"__class__": "QualitativeValue",
"attribution": {
"__class__": "Attribution",
"first_acquired": null,
"sources": []
},
"value": [
"3216"
]
},
"lead_free_status": {
"__class__": "QualitativeValue",
"attribution": {
"__class__": "Attribution",
"first_acquired": null,
"sources": [
{
"__class__": "Source",
"name": "Future Electronics",
"uid": "e4032109c4f337c4"
}
]
},
"value": [
"Lead Free"
]
},
"lifecycle_status": {
"__class__": "QualitativeValue",
"attribution": {
"__class__": "Attribution",
"first_acquired": null,
"sources": []
},
"value": [
"Not Listed by Manufacturer"
]
},
"pin_count": {
"__class__": "QuantitativeValue",
"attribution": {
"__class__": "Attribution",
"first_acquired": null,
"sources": [
{
"__class__": "Source",
"name": "Farnell",
"uid": "58989d9272cd8b5f"
}
]
},
"max_value": null,
"min_value": null,
"unit": null,
"value": [
"2"
]
},
"power_rating": {
"__class__": "QuantitativeValue",
"attribution": {
"__class__": "Attribution",
"first_acquired": null,
"sources": [
{
"__class__": "Source",
"name": "Newark",
"uid": "d294179ef2900153"
}
]
},
"max_value": null,
"min_value": null,
"unit": null,
"value": [
"0.5"
]
},
"resistance": {
"__class__": "QuantitativeValue",
"attribution": {
"__class__": "Attribution",
"first_acquired": null,
"sources": [
{
"__class__": "Source",
"name": "Farnell",
"uid": "58989d9272cd8b5f"
}
]
},
"max_value": null,
"min_value": null,
"unit": null,
"value": [
"0.01"
]
},
"resistance_tolerance": {
"__class__": "QualitativeValue",
"attribution": {
"__class__": "Attribution",
"first_acquired": null,
"sources": []
},
"value": [
"\u00b11%"
]
},
"rohs_status": {
"__class__": "QualitativeValue",
"attribution": {
"__class__": "Attribution",
"first_acquired": null,
"sources": [
{
"__class__": "Source",
"name": "Newark",
"uid": "d294179ef2900153"
}
]
},
"value": [
"Compliant"
]
}
},
"uid": "69e8a09b8cb4b62f",
"uid_v2": 797906654705
}
],
"reference": null
}
]
}
Yes, you will need a custom JsonConverter to solve this. Basically what you need to do is this:
Define your QualitativeValue and QuantitativeValue classes to have a common base class (e.g. AbstractQValue) or interface.
In your Parts class, make the specs property a Dictionary<string, AbstractQValue>. This will handle the changing attribute names.
Create a custom JsonConverter to handle the instantiation of the concrete QualitativeValue or QuantitativeValue based on the __class__ attribute in the JSON. See this answer for an example of how to implement this.
Lastly, when you do your deserialization, be sure to pass an instance of the custom JsonConverter into the JsonConvert.DeserializeObject method.
Demo
I had a little time, so I threw together a working example. Here are the class definitions for the data (I cut out most of the extraneous stuff for brevity):
public class PartsMatchResponse
{
public List<PartsMatchResult> results { get; set; }
}
public class PartsMatchResult
{
public List<Part> items { get; set; }
}
public class Part
{
public Manufacturer manufacturer { get; set; }
public string mpn { get; set; }
public Dictionary<string, AbstractQValue> specs { get; set; }
}
public class Manufacturer
{
public string name { get; set; }
}
public abstract class AbstractQValue
{
public List<string> value { get; set; }
}
public class QualitativeValue : AbstractQValue
{
}
public class QuantitativeValue : AbstractQValue
{
public string unit { get; set; }
}
Here is the custom JsonConverter class:
public class QValueJsonConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return typeof(AbstractQValue).IsAssignableFrom(objectType);
}
public override object ReadJson(JsonReader reader,
Type objectType, object existingValue, JsonSerializer serializer)
{
JObject jo = JObject.Load(reader);
if (jo["__class__"].ToString() == "QuantitativeValue")
{
return jo.ToObject<QuantitativeValue>();
}
return jo.ToObject<QualitativeValue>();
}
public override void WriteJson(JsonWriter writer,
object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
Here is a demo program showing how to use the converter when deserializing:
class Program
{
static void Main(string[] args)
{
// (jsonString is defined as a constant below)
PartsMatchResponse response =
JsonConvert.DeserializeObject<PartsMatchResponse>(jsonString,
new QValueJsonConverter());
foreach (Part part in response.results[0].items)
{
Console.WriteLine("manufacturer: " + part.manufacturer.name);
Console.WriteLine("mfr. part no: " + part.mpn);
foreach (KeyValuePair<string, AbstractQValue> kvp in part.specs)
{
string unit = "";
if (kvp.Value is QuantitativeValue)
unit = ((QuantitativeValue)kvp.Value).unit;
Console.WriteLine(kvp.Key + ": " +
string.Join(", ", kvp.Value.value) + " " + unit);
}
Console.WriteLine();
}
}
// Note: this is the same as the example JSON in the question, except
// I added units for some of the QuantitativeValue specs for demo purposes.
const string jsonString = #"
{
""__class__"": ""PartsMatchResponse"",
""msec"": 183,
""request"": {
""__class__"": ""PartsMatchRequest"",
""exact_only"": false,
""queries"": [
{
""__class__"": ""PartsMatchQuery"",
""brand"": null,
""limit"": 10,
""mpn"": ""ERJ8BWFR010V"",
""mpn_or_sku"": null,
""q"": """",
""reference"": null,
""seller"": null,
""sku"": null,
""start"": 0
}
]
},
""results"": [
{
""__class__"": ""PartsMatchResult"",
""error"": null,
""hits"": 1,
""items"": [
{
""__class__"": ""Part"",
""brand"": {
""__class__"": ""Brand"",
""name"": ""Panasonic - ECG"",
""uid"": ""4c528d5878c09b95""
},
""category_uids"": [
""7542b8484461ae85"",
""cd01000bfc2916c6"",
""5c6a91606d4187ad""
],
""compliance_documents"": [],
""datasheets"": null,
""external_links"": {
""__class__"": ""ExternalLinks"",
""evalkit_url"": null,
""freesample_url"": null,
""product_url"": null
},
""imagesets"": null,
""manufacturer"": {
""__class__"": ""Manufacturer"",
""name"": ""Panasonic - ECG"",
""uid"": ""c20a0700af7c11cd""
},
""mpn"": ""ERJ8BWFR010V"",
""octopart_url"": ""http://octopart.com/erj8bwfr010v-panasonic+-+ecg-7979066"",
""offers"": null,
""specs"": {
""case_package"": {
""__class__"": ""QualitativeValue"",
""attribution"": {
""__class__"": ""Attribution"",
""first_acquired"": null,
""sources"": []
},
""value"": [
""1206""
]
},
""case_package_si"": {
""__class__"": ""QualitativeValue"",
""attribution"": {
""__class__"": ""Attribution"",
""first_acquired"": null,
""sources"": []
},
""value"": [
""3216""
]
},
""lead_free_status"": {
""__class__"": ""QualitativeValue"",
""attribution"": {
""__class__"": ""Attribution"",
""first_acquired"": null,
""sources"": [
{
""__class__"": ""Source"",
""name"": ""Future Electronics"",
""uid"": ""e4032109c4f337c4""
}
]
},
""value"": [
""Lead Free""
]
},
""lifecycle_status"": {
""__class__"": ""QualitativeValue"",
""attribution"": {
""__class__"": ""Attribution"",
""first_acquired"": null,
""sources"": []
},
""value"": [
""Not Listed by Manufacturer""
]
},
""pin_count"": {
""__class__"": ""QuantitativeValue"",
""attribution"": {
""__class__"": ""Attribution"",
""first_acquired"": null,
""sources"": [
{
""__class__"": ""Source"",
""name"": ""Farnell"",
""uid"": ""58989d9272cd8b5f""
}
]
},
""max_value"": null,
""min_value"": null,
""unit"": null,
""value"": [
""2""
]
},
""power_rating"": {
""__class__"": ""QuantitativeValue"",
""attribution"": {
""__class__"": ""Attribution"",
""first_acquired"": null,
""sources"": [
{
""__class__"": ""Source"",
""name"": ""Newark"",
""uid"": ""d294179ef2900153""
}
]
},
""max_value"": null,
""min_value"": null,
""unit"": ""Watt"",
""value"": [
""0.5""
]
},
""resistance"": {
""__class__"": ""QuantitativeValue"",
""attribution"": {
""__class__"": ""Attribution"",
""first_acquired"": null,
""sources"": [
{
""__class__"": ""Source"",
""name"": ""Farnell"",
""uid"": ""58989d9272cd8b5f""
}
]
},
""max_value"": null,
""min_value"": null,
""unit"": ""Ohm"",
""value"": [
""0.01""
]
},
""resistance_tolerance"": {
""__class__"": ""QualitativeValue"",
""attribution"": {
""__class__"": ""Attribution"",
""first_acquired"": null,
""sources"": []
},
""value"": [
""\u00b11%""
]
},
""rohs_status"": {
""__class__"": ""QualitativeValue"",
""attribution"": {
""__class__"": ""Attribution"",
""first_acquired"": null,
""sources"": [
{
""__class__"": ""Source"",
""name"": ""Newark"",
""uid"": ""d294179ef2900153""
}
]
},
""value"": [
""Compliant""
]
}
},
""uid"": ""69e8a09b8cb4b62f"",
""uid_v2"": 797906654705
}
],
""reference"": null
}
]
}";
}
And finally, here is the output of the above program:
manufacturer: Panasonic - ECG
mfr. part no: ERJ8BWFR010V
case_package: 1206
case_package_si: 3216
lead_free_status: Lead Free
lifecycle_status: Not Listed by Manufacturer
pin_count: 2
power_rating: 0.5 Watt
resistance: 0.01 Ohm
resistance_tolerance: ±1%
rohs_status: Compliant
You'll need these classes to model the JSON
public class OctopartObject
{
public string __class__ { get; set; }
public int msec { get; set; }
public Request request { get; set; }
public List<Result> results { get; set; }
}
public class Query
{
public string __class__ { get; set; }
public object brand { get; set; }
public int limit { get; set; }
public string mpn { get; set; }
public object mpn_or_sku { get; set; }
public string q { get; set; }
public object reference { get; set; }
public object seller { get; set; }
public object sku { get; set; }
public int start { get; set; }
}
public class Request
{
public string __class__ { get; set; }
public bool exact_only { get; set; }
public List<Query> queries { get; set; }
}
public class Brand
{
public string __class__ { get; set; }
public string name { get; set; }
public string uid { get; set; }
}
public class Manufacturer
{
public string __class__ { get; set; }
public string name { get; set; }
public string uid { get; set; }
}
public class Item
{
public string __class__ { get; set; }
public Brand brand { get; set; }
public Manufacturer manufacturer { get; set; }
public string mpn { get; set; }
public string octopart_url { get; set; }
public List<object> offers { get; set; }
public string uid { get; set; }
public object uid_v2 { get; set; }
}
public class Result
{
public string __class__ { get; set; }
public object error { get; set; }
public int hits { get; set; }
public List<Item> items { get; set; }
public object reference { get; set; }
}
Then using JSON.NET & .NET 4.5, do something like this.
HttpClient client = new HttpClient();
// Send a request asynchronously and continue when complete
HttpResponseMessage clientResult = await client.GetAsync(_address);
// Check that response was successful or throw exception
clientResult.EnsureSuccessStatusCode();
// Read response asynchronously as JToken and write out top facts for each country
string jsonString = await clientResult.Content.ReadAsStringAsync();
OctopartObject obj = JsonConvert.DeserializeObject<OctopartObject>(jsonString);
You'll have a nice object that should model the data received from the _address URI
I still haven't fully tested this, so there could be some issues. But I've been struggling for a few hours on this and finally found something that seems to be working. I'm sure it won't work for datasheets and any extra fields, as this only returns the basic. But I essentially just used this site to get the object models and changed the name of the root one to OctopartObject

Categories