C
C#13mo ago
LazyGuard

✅ How to parse this weird API ?

I have a very akward thing to do and I d'ont know if there is a clean way to do it. In a REST API, there is two string fields attributeName and attributeValue (for example { "attributeName" : "isAllowed" , " attributeValue" : "true"} and { "attributeName" : "productType" , " attributeValue" : "A50"} , in the API this is modelled by the following class N.B: the "attribute" here have nothing to do with any .NET vocabulary, it's just a name, think of it like "foo" or "bar"
public record ApiCommand
{
public string? AttributeName {get; init;}
public string? AttributeValue {get; init;}
}
public record ApiCommand
{
public string? AttributeName {get; init;}
public string? AttributeValue {get; init;}
}

In my Domain I have a class with all possible properties
public record Porduct
{
public bool? IsAllowed {get; init;}
public ProductType? ProductType {get; init;} // ProductType is an enum
// There are other properties as well
}
public record Porduct
{
public bool? IsAllowed {get; init;}
public ProductType? ProductType {get; init;} // ProductType is an enum
// There are other properties as well
}

what I need to do is, depending on the AttributeName and AttributeValue given in the API, I want to create an instance of Product which has all everything equal to null except for the property for which we were given the name and value in AttributeName and AttributeValue. (there is always a single one) For exemple { "attributeName" : "isAllowed" , " attributeValue" : "true"} would result in a product Instance with product.IsAllowed = true and product.ProductType = null (and other fields will be null as well) Another example, { "attributeName" : "productType" , " attributeValue" : "A50"} would result in product Instance with product.IsAllowed = null and product.ProductType = A50 (and other fields will be null as well)
64 Replies
LazyGuard
LazyGuard13mo ago
. my attempt is something that i find very ugly:, it's something like this:
produt = new Product() // an instance where everything is null;
if (model.AttributeName.toLower() == "isallowed")
{
if (model.AttributeValue == "true")
{
return new product with {IsAllowed = true}
}
else if (model.AttributeValue == "false")
{
return new product with {IsAllowed = false}
}
//else return some error
}
// other ifs for other properties like productType
produt = new Product() // an instance where everything is null;
if (model.AttributeName.toLower() == "isallowed")
{
if (model.AttributeValue == "true")
{
return new product with {IsAllowed = true}
}
else if (model.AttributeValue == "false")
{
return new product with {IsAllowed = false}
}
//else return some error
}
// other ifs for other properties like productType
for 10 properties to handle, it will be a huge mess :/ Any help ?
sibber
sibber13mo ago
make a method in ApiCommand that maps to Product btw, ApiCommand is what we call a data transfer object (DTO), and the conventional way to name it is ProductDTO oh wait i think i misunderstood you
daysleeper
daysleeper13mo ago
Attributes and reflection
Use attributes to associate metadata or declarative information with code in C#. An attribute can be queried at run time by using reflection. Reflection provides objects that describe assemblies, modules, and types in C#. If your code includes attributes, reflection enables you to access them.
sibber
sibber13mo ago
ApiCommand returns the value of a single property for a product? thats not what they mean by attribute
LazyGuard
LazyGuard13mo ago
yeah, attribute belongs to a domain vocabulary, that have nothing to do with the meaning of an attribute in C# 😆 I will edit my Original post to be more clear
sibber
sibber13mo ago
^
LazyGuard
LazyGuard13mo ago
yup ApiCommand always have the name and value of a signle property of product
sibber
sibber13mo ago
what a shitty api so that means you need to query the endpoint for each property of Product?
LazyGuard
LazyGuard13mo ago
no it's more complicated I tried to create a simple example to be able to communicate what I need to do unfortunately I don't have the ability to change the ApiCpmmand you can think of it as a product will always have everything null except for one property
sibber
sibber13mo ago
i dont see how thats different from what i said
daysleeper
daysleeper13mo ago
i understand that. reflection can help you access metadata like
var type = typeof(Model);
var properties = type.GetProperties();
foreach(var property in properties)
{
var propertyName = property.Name; (that's where you can compare them or something)
// do something
}
var type = typeof(Model);
var properties = type.GetProperties();
foreach(var property in properties)
{
var propertyName = property.Name; (that's where you can compare them or something)
// do something
}
sibber
sibber13mo ago
im aware at that point i didnt understand op's question but yeah that could be useful, although i still dont fully understand what op is trying to do lol
LazyGuard
LazyGuard13mo ago
The endpoind is a POST. When the body has { "attributeName" : "isAllowed" , " attributeValue" : "true"} it would result in creating a Product Instance with product.IsAllowed = true and product.ProductType = null (and all other properties will be null as well). That instance will then be saved in the Db
sibber
sibber13mo ago
ah and you just need to set this one property in your model
LazyGuard
LazyGuard13mo ago
yup everytime it's just one property
sibber
sibber13mo ago
do you make the post req in the same app? obviously, that was a stupid question lol i meant to ask, how do you make the post requeist how do you provide attributeName
LazyGuard
LazyGuard13mo ago
it's exposed by my app to the users who have some client coded
sibber
sibber13mo ago
wait youre writing the api? so youre recieving the post request? the ApiCommand instance?
LazyGuard
LazyGuard13mo ago
yup
sibber
sibber13mo ago
ah alright, you have 2 options: 1. use reflection 2. write a source gen id go with 1 in your case
LazyGuard
LazyGuard13mo ago
😢 I feel that it's very shitty
sibber
sibber13mo ago
its not its made for things like this thats what a lot of serializers use actually theres a third option re design your api
LazyGuard
LazyGuard13mo ago
I can't :(, it's designed by some """"""tech lead""""""" :/
sibber
sibber13mo ago
oof welp reflection it is
LazyGuard
LazyGuard13mo ago
If it was me I would go with something that mirror Product in the Api :/
sibber
sibber13mo ago
yes thats what sane people would do unless im missing some context
LazyGuard
LazyGuard13mo ago
if I try to do this using reflection, can't see how this is helpful :/
var type = typeof(Model);
var properties = type.GetProperties();
foreach(var property in properties)
{
var propertyName = property.Name; //(that's where you can compare them or something)
// do something
}
var type = typeof(Model);
var properties = type.GetProperties();
foreach(var property in properties)
{
var propertyName = property.Name; //(that's where you can compare them or something)
// do something
}
(it's the message posted by @daysleeper00 ) ah, Model here is Product ? I thought it was ApiCommand
daysleeper
daysleeper13mo ago
you can first validate fields by searching for them and then invoke a method like UpdateFields() where invoke property.SetValue(). i think its pretty easy if you research a bit about reflection
sibber
sibber13mo ago
well what you want to do is get the property named ApiCommand.attributeName
LazyGuard
LazyGuard13mo ago
but i still need to compare apiCommand.AttributeName with property.Name for every preperty in properties. = Product.GetProperties() right ?
sibber
sibber13mo ago
no read the doc i sent GetProperty returns null if the property doesnt exist actually maybe that would be faster if you cache the property infos
LazyGuard
LazyGuard13mo ago
still can't see how to use it 😆 , something like product.GetProperty("isAllowed") won't help me the input is ApiCommand. and not the Product
daysleeper
daysleeper13mo ago
read about reflection first and when you understand how it works you will get this done trust me
LazyGuard
LazyGuard13mo ago
okay !
sibber
sibber13mo ago
did you read this?
Pobiega
Pobiega13mo ago
How many properties are there? Are they likely to change in the future?
sibber
sibber13mo ago
this is in a post endpoint theyre recieving an ApiCommand instance, and they want to create a Product and set the property that is in ApiCommand thats the TLDR
Pobiega
Pobiega13mo ago
I'm aware.
sibber
sibber13mo ago
ah
Pobiega
Pobiega13mo ago
I'm asking for how many properties there are on Product
sibber
sibber13mo ago
oh
Pobiega
Pobiega13mo ago
if its a low number, and not likely to change, a reflection based approach seems overkill
sibber
sibber13mo ago
oh yeah if its low enough you could switch on ApiCommand.balblaName idk why i didnt think of that
Pobiega
Pobiega13mo ago
actually, a source generator would be a very cool solution.. and also very likely massively overkill in terms of effort 😄 exactly
LazyGuard
LazyGuard13mo ago
there is ~15 properties
Pobiega
Pobiega13mo ago
alright. thats... doable on a switch, but might not feel very clean.
LazyGuard
LazyGuard13mo ago
I did but not sure I fully understand it :/. It lacks some examples
sibber
sibber13mo ago
look at the last overload
LazyGuard
LazyGuard13mo ago
i guess I need to do something like
var property = Product.GetProperty(apiCommand.AttributeName)
// then something like product.property = apiCommand.AttributeValue, not sure about the syntax, I guess there will be some method to do something like this
var property = Product.GetProperty(apiCommand.AttributeName)
// then something like product.property = apiCommand.AttributeValue, not sure about the syntax, I guess there will be some method to do something like this
Pobiega
Pobiega13mo ago
the second part will have nasty syntax.. something like property.SetValue(product, value); where value is a correctly typed variable holding the parsed content from AttributeValue string, just shove it in. int/bool etc? gotta parse
sibber
sibber13mo ago
typeof(Product).GetProperty although caching the property infos and querying them might be a faster approach, if you care about performance you should benchmark it first tho
LazyGuard
LazyGuard13mo ago
yeah that feels like a very strange syntax yup, I also need to parse strings to properly tyed stuff what do you mean by caching it ?
sibber
sibber13mo ago
storing it somewhere and accessing it not using reflection to get all the properties every time
LazyGuard
LazyGuard13mo ago
Okay, I will give reflection a try, then benchmark it and then maybe cache it in some config file (app.settings for instance)
sibber
sibber13mo ago
dont cache it in a config file that wouldnt work anyway cache it in the class youre doing that in
Pobiega
Pobiega13mo ago
you'd set up a dictionary or similar, between your string (property name) and the propertyinfo and calculate that once, at startup because reflection is expensive (time-wasting) so doing it once and then storing the results is good practice
sibber
sibber13mo ago
oh right thats a better idea
LazyGuard
LazyGuard13mo ago
what do you mean by "propertyInfo" ?
sibber
sibber13mo ago
look at what this returns
Pobiega
Pobiega13mo ago
Thats the type you get from typeof(T).GetProperty
LazyGuard
LazyGuard13mo ago
Oh yeah I see, it's still using relfection by only once using some static field to store the result thanks folks
Accord
Accord13mo ago
Was this issue resolved? If so, run /close - otherwise I will mark this as stale and this post will be archived until there is new activity.
Want results from more Discord servers?
Add your server
More Posts