C
C#•12mo ago
Pannekoekje

Casting, reflection or?

I'm trying to write a simple lib for consuming a few JSON API endpoints and processing their contents with callbacks. I'm trying to cast the "parsed" variable to its actual class (as it is known during runtime). Psuedo code as follows:
public interface IType
{
public string Type { get; set; }
}

public class TypeA : IType
{
public string Type { get; set; }
public string FancyA { get; set; }
}

public class TypeB : IType
{
public string Type { get; set; }
public string FancyB { get; set; }
}

void Get(json)
{
var x = GetJson;
IType returnObject = null;

switch(x.GetType)
{
"TypeA":
returnObject = JsonSerializer.Deserialize<TypeA>(x);
break;
"TypeB":
returnObject = JsonSerializer.Deserialize<TypeB>(x);
break;
}
return returnObject;
}

void DoParse()
{

var callbacks = Dictionary<string, Action<IType>> { ("TypeA", DoStuffTypeA) };

while(true)
{
var content = Retrieve();
var parsed = Get(content);

var callback = callbacks[parsed.Type]
callback.Invoke(parsed)
}
}

// Works
void DoStuffTypeA(IType content)
{
//...
}


// Doesn't work
void DoStuffTypeA(TypeA content)
{
//...
}
public interface IType
{
public string Type { get; set; }
}

public class TypeA : IType
{
public string Type { get; set; }
public string FancyA { get; set; }
}

public class TypeB : IType
{
public string Type { get; set; }
public string FancyB { get; set; }
}

void Get(json)
{
var x = GetJson;
IType returnObject = null;

switch(x.GetType)
{
"TypeA":
returnObject = JsonSerializer.Deserialize<TypeA>(x);
break;
"TypeB":
returnObject = JsonSerializer.Deserialize<TypeB>(x);
break;
}
return returnObject;
}

void DoParse()
{

var callbacks = Dictionary<string, Action<IType>> { ("TypeA", DoStuffTypeA) };

while(true)
{
var content = Retrieve();
var parsed = Get(content);

var callback = callbacks[parsed.Type]
callback.Invoke(parsed)
}
}

// Works
void DoStuffTypeA(IType content)
{
//...
}


// Doesn't work
void DoStuffTypeA(TypeA content)
{
//...
}
Is this just impossible or am I implenting this stuff wrong?
44 Replies
Jimmacle
Jimmacle•12mo ago
what does the json look like? AFAIK STJ supports using a discriminator value for serializing subclasses
Pannekoekje
PannekoekjeOP•12mo ago
What is STJ?
Jimmacle
Jimmacle•12mo ago
System.Text.Json the C# json library
Pannekoekje
PannekoekjeOP•12mo ago
All of the psuede code works, Except for when I try to run the "callback" with the TypeA signature I don't know why it doesn't accept the implementation of an interface
Jimmacle
Jimmacle•12mo ago
you mean in your dictionary? because the signature isn't compatible your dictionary holds Action<IType> you can't give it a method that only accepts a specific implementation
Pannekoekje
PannekoekjeOP•12mo ago
I tried changing that ot Action<Object> but that also didn't take
Jimmacle
Jimmacle•12mo ago
if it's specialized, just cast it inside the method
Pannekoekje
PannekoekjeOP•12mo ago
Hmm I've been doing that but just think that is ulgy
Jimmacle
Jimmacle•12mo ago
but this code looks off to me in general which is why i was asking what the json actually looks like why are you getting the type of whatever GetJson returns and using that to decide how to deserialize?
Pannekoekje
PannekoekjeOP•12mo ago
It can be a lot of stuff, there are 2 commons fields but otherwise there can be objects in there, strings bools what have you
Jimmacle
Jimmacle•12mo ago
so it's completely dynamic and not a fixed set of possible models?
Pannekoekje
PannekoekjeOP•12mo ago
There is a fixed set
Jimmacle
Jimmacle•12mo ago
you should model that properly in your code
Pannekoekje
PannekoekjeOP•12mo ago
How would I go about doing that?
Jimmacle
Jimmacle•12mo ago
i don't know, your Get method doesn't make sense the way i'm looking at it
Pannekoekje
PannekoekjeOP•12mo ago
Its on a websocket, so all models can come in whenever
Jimmacle
Jimmacle•12mo ago
how do you know which type a message is?
Pannekoekje
PannekoekjeOP•12mo ago
Like so
var jsonObject = JsonDocument.Parse(inputJson);
var jsonString = Encoding.UTF8.GetString(inputJson.ToArray());
var responseContent = new JsonElement();

IType returnObject = null;

if (jsonObject.RootElement.TryGetProperty("error", out JsonElement error))
var jsonObject = JsonDocument.Parse(inputJson);
var jsonString = Encoding.UTF8.GetString(inputJson.ToArray());
var responseContent = new JsonElement();

IType returnObject = null;

if (jsonObject.RootElement.TryGetProperty("error", out JsonElement error))
And other "TryGetProperties" for other "top-level" action indicators
Jimmacle
Jimmacle•12mo ago
so there is no actual discriminator, you just have to see if it looks like a certain type?
Pannekoekje
PannekoekjeOP•12mo ago
Correct
Jimmacle
Jimmacle•12mo ago
that's gross do you have control over that?
Pannekoekje
PannekoekjeOP•12mo ago
No
if (jsonObject.RootElement.TryGetProperty("action", out JsonElement jsonAction))
{
switch (jsonAction.ToString())
{
case "getTime":
xx
case "getWindow":
xx
if (jsonObject.RootElement.TryGetProperty("action", out JsonElement jsonAction))
{
switch (jsonAction.ToString())
{
case "getTime":
xx
case "getWindow":
xx
And such
Jimmacle
Jimmacle•12mo ago
that looks exactly like the discriminator i'm asking about, is action not always present in a normal message?
Pannekoekje
PannekoekjeOP•12mo ago
No
Jimmacle
Jimmacle•12mo ago
Harold
Pannekoekje
PannekoekjeOP•12mo ago
Wait I'll double check to be sure It's only the "error" and "action" ones but maybe "error" has "action"
Jimmacle
Jimmacle•12mo ago
i would try to find a good candidate for a discriminator because then you can use that with STJ's polymorphic serialization
Jimmacle
Jimmacle•12mo ago
then all you have to do to handle each message is let STJ deserialize it then pattern match on the type
Pannekoekje
PannekoekjeOP•12mo ago
Ah, I initially wrote this in .net 6
Jimmacle
Jimmacle•12mo ago
should still be available
Pannekoekje
PannekoekjeOP•12mo ago
"Beginning with .NET 7, System.Text.Json supports polymorphic type hierarchy serialization and deserialization with attribute annotations."
Jimmacle
Jimmacle•12mo ago
or not
Pannekoekje
PannekoekjeOP•12mo ago
I can update no problem, so I'll read this thx 🙂 But even with this, I'd still be casting in the DoStuffTypeA right This is simplifying the parsing, not the typing
Jimmacle
Jimmacle•12mo ago
correct, you'd still have to decide what to do based on what the message is which i wouldn't use a dictionary for at all, just pattern match
switch (msg)
{
case MsgA a:
DoSomething(a);
break;
case MsgB b:
DoSomethingElse(b)
break;
}
switch (msg)
{
case MsgA a:
DoSomething(a);
break;
case MsgB b:
DoSomethingElse(b)
break;
}
etc
Pannekoekje
PannekoekjeOP•12mo ago
its a lib, so that wouldn't work As I'd have to know the methods used in the program beforehand That's why I use the dict
Jimmacle
Jimmacle•12mo ago
do you know the messages beforehand or can those also be different?
Pannekoekje
PannekoekjeOP•12mo ago
Which ones do you mean?
Jimmacle
Jimmacle•12mo ago
the ones you're deserializing
Pannekoekje
PannekoekjeOP•12mo ago
those I know beforehand Except if the API changes, then I'd have to update the lib But that is fine
Jimmacle
Jimmacle•12mo ago
so you could also just have a class that holds callbacks that the user can set individually like
class Handlers
{
Action<MsgA> A { get; set; }
Action<MsgB> B { get; set; }
}
class Handlers
{
Action<MsgA> A { get; set; }
Action<MsgB> B { get; set; }
}
Pannekoekje
PannekoekjeOP•12mo ago
That would work
Jimmacle
Jimmacle•12mo ago
it avoids the problem of having to find a common signature for all the handlers
Pannekoekje
PannekoekjeOP•12mo ago
As well as not having to do reflection I stayed away from that type of implementation because I'd have to double everything (in .net 6) but seeing as this new feature will probably reduce my parsing file from 300 lines to a 20 line function, that'd be fine Guess I have a little rewrite to do, thanks :)!
Want results from more Discord servers?
Add your server