How to find "common" types of exceptions in C#?



  • So, I've got this method call here:

    T? result = await HttpClient.GetFromJsonAsync<T>(string url);
    

    and now I'm trying to figure out which exceptions can be commonly thrown by this - i.e. "no network connection" versus "server does not answer" versus "404" versus "JSON parse failed" and so on.

    Is there some way to easily discover the exception types or do I have to manually create every error condition I can think of?


  • Grade A Premium Asshole

    @Rhywden said in How to find "common" types of exceptions in C#?:

    do I have to manually create every error condition I can think of?

    I usually create them accidentally. Whether you consider that to be strictly manual is up to you.


  • Discourse touched me in a no-no place

    @Rhywden said in How to find "common" types of exceptions in C#?:

    Is there some way to easily discover the exception types

    And that's the problem with having all exceptions be unchecked.

    Since it's networking, and HTTP, and JSON, you'll have any general I/O failure, and any networking failure, and any HTTP failure, and any JSON parsing problem (if the message type doesn't provide critical information to construct a T from). That's a very large number of types of failure, and there isn't really a good way to narrow it down. (You could also have general problems like memory allocation failures and so on, but they're less likely.) There isn't really a good way to prevent those sorts of issues from happening so you're just going to have to handle, well, anything. You might want to try to spot common things (like the network not working) and handle them better as they're pretty likely to happen in practice, even if only rarely.



  • @dkf Ah, okay, then. Hrmph. I just wish there was a way to filter for "common" exceptions / error conditions and the "exotic" ones.

    Kind of like:

    try {
       OneOf<T, ServerError, NotFound, ParseInvalid> result = await HttpClient.GetFromJsonAsync<T>(url);
    } catch(Exception e) {
       //deal with more "outlandish" stuff here
    }
    

  • Discourse touched me in a no-no place

    @Rhywden You probably want to handle problems relating to networking/wrong HTTP responses as fairly common cases. Wrong serialization info should be less common... unless some idiot's decided to do 200 ERROR but that all depends on whether you control the service you are querying.

    And if you ever cancel the waiting, you need to handle that too.

    Failures happen. Often what separates beginner code from expert code is how failures are dealt with.


  • Considered Harmful

    Find one, chase up to the most common in-library supertype, examine library implementations. A lot of (Java) libraries will put in a distinction between recoverable/unrecoverable, remote/local at the supertype level.


  • Considered Harmful

    @Rhywden there probably is, see above.


  • Considered Harmful

    Used to be that MSDN listed the exceptions thrown specifically by the method or property, but this Json extension thing does not. You being 🇩🇪 could leave an angry letter in some feedback place.



  • You can refer to here

    [quote]

    As we can see, every operation is reduced to a single line of code. Under the hood, it uses JsonSerializer for serialization/deserialization, so for example (besides the classic HttpClient exceptions) we obtain a JsonException if the JSON is invalid. Moreover, if the content type is incorrect for JSON requests, we get a NotSupportedException. And we don’t need to pass anymore a JsonSerializerOptions object to these methods, because they automatically use one with Camel case naming policy and case-insensitive comparison during deserialization (as we normally expect when working with JSON).

    [/quote]


  • And then the murders began.

    @Rhywden What are you planning to do with the exceptions?

    If there's cases where you'd retry: you're better off using a library like Polly which is dedicated to that.

    If it's not to do a retry: then why do you care what type it is?



  • @cheong said in How to find "common" types of exceptions in C#?:

    As we can see, every operation is reduced to a single line of code. Under the hood, it uses JsonSerializer for serialization/deserialization, so for example (besides the classic HttpClient exceptions) we obtain a JsonException if the JSON is invalid. Moreover, if the content type is incorrect for JSON requests, we get a NotSupportedException.

    … and I suppose anything at all actually because any complex types may define their JSON deserialization themselves and quite likely can throw up anything and everything from there.

    @cheong said in How to find "common" types of exceptions in C#?:

    And we don’t need to pass anymore a JsonSerializerOptions object to these methods, because they automatically use one with Camel case naming policy and case-insensitive comparison during deserialization (as we normally expect when working with JSON).

    On a side note, I strongly disagree with the case-insensitive part. JavaScript is case sensitive, and JSON specification does not say anything about case-insensitive anything either, so I definitely expect binary equality comparison.



  • @Bulb said in How to find "common" types of exceptions in C#?:

    @cheong said in How to find "common" types of exceptions in C#?:

    As we can see, every operation is reduced to a single line of code. Under the hood, it uses JsonSerializer for serialization/deserialization, so for example (besides the classic HttpClient exceptions) we obtain a JsonException if the JSON is invalid. Moreover, if the content type is incorrect for JSON requests, we get a NotSupportedException.

    … and I suppose anything at all actually because any complex types may define their JSON deserialization themselves and quite likely can throw up anything and everything from there.

    I suppose I should highlight JsonException and possibly also NotSupportedException when quoting.

    @cheong said in How to find "common" types of exceptions in C#?:

    And we don’t need to pass anymore a JsonSerializerOptions object to these methods, because they automatically use one with Camel case naming policy and case-insensitive comparison during deserialization (as we normally expect when working with JSON).

    On a side note, I strongly disagree with the case-insensitive part. JavaScript is case sensitive, and JSON specification does not say anything about case-insensitive anything either, so I definitely expect binary equality comparison.

    I think that part just describe how the JSON is mapped into class properties when the names are not explicitly defined.with JsonPropertyNameAttribute or something.



  • @cheong said in How to find "common" types of exceptions in C#?:

    I suppose I should highlight JsonException and possibly also NotSupportedException when quoting.

    No, you shouldn't. I've seen those. It's just two extra exceptions. That's not what I mean. I mean anything as in really anything. Because the set of JsonConverter and JsonConverterFactory subclasses it may end up calling is open-ended.



  • @cheong said in How to find "common" types of exceptions in C#?:

    I think that part just describe how the JSON is mapped into class properties when the names are not explicitly defined.with JsonPropertyNameAttribute or something.

    Yes. And that should be case sensitive, because there are no good reason to make it otherwise. And it is the default of the JsonSerializer, but the HttpClient overrides it.



  • @Unperverted-Vixen said in How to find "common" types of exceptions in C#?:

    @Rhywden What are you planning to do with the exceptions?

    If there's cases where you'd retry: you're better off using a library like Polly which is dedicated to that.

    If it's not to do a retry: then why do you care what type it is?

    It was actually more of a curiosity when I was thinking about how this operation might fail and what to present to the user in each case. This was merely the first example I thought of because I was just using this function. I'm not actually intending to do more than grabbing the exception message and displaying that as it's only for an internal tool for myself.

    Call it a thought experiment to see whether it can be done or yea, should be done and if so, in which cases. Or which alternatives to consider as you yourself pointed out.



  • @Bulb said in How to find "common" types of exceptions in C#?:

    @cheong said in How to find "common" types of exceptions in C#?:

    I think that part just describe how the JSON is mapped into class properties when the names are not explicitly defined.with JsonPropertyNameAttribute or something.

    Yes. And that should be case sensitive, because there are no good reason to make it otherwise. And it is the default of the JsonSerializer, but the HttpClient overrides it.

    Say, when the JSON has a property "class-name" (Yup, that's legal as long as the key is double-quoted), the corresponding property in C# would be ClassName.

    But this ClassName C# property can also got mapped automatically for JSON properties "ClassName", and "className" too. How else you can cater for naming convention of data servers coded in different programming languages, without polluting your own?



  • @cheong said in How to find "common" types of exceptions in C#?:

    (Yup, that's legal as long as the key is double-quoted)

    As of course it must be for JSON.



  • @cheong said in How to find "common" types of exceptions in C#?:

    @Bulb said in How to find "common" types of exceptions in C#?:

    @cheong said in How to find "common" types of exceptions in C#?:

    I think that part just describe how the JSON is mapped into class properties when the names are not explicitly defined.with JsonPropertyNameAttribute or something.

    Yes. And that should be case sensitive, because there are no good reason to make it otherwise. And it is the default of the JsonSerializer, but the HttpClient overrides it.

    Say, when the JSON has a property "class-name" (Yup, that's legal as long as the key is double-quoted), the corresponding property in C# would be ClassName.

    And you'll have to annotate it. And that's fine. Everybody does.

    But this ClassName C# property can also got mapped automatically for JSON properties "ClassName", and "className" too.

    And that's wrong, because JSON property can't have alternate names. The schema has no way of specifying that. So make up your mind and only map one of them. You have to make up your mind for serialization anyway.

    How else you can cater for naming convention of data servers coded in different programming languages, without polluting your own?

    With annotations just like everybody else. All Go code I've ever seen the serialized names annotated, and unlike in C#, in Go they can't use camelCase names directly, because in Go the distinction between upper and lowercase initial letter has a semantic meaning. And yet they don't mind. So in Go it would be

    ClassName string `json:"className"`
    

    You could even have an annotation on the type something like [Json(naming="camelCase")] and just do the automatic conversion. But Go does not and they don't seem to mind anyway.



  • @Bulb said in How to find "common" types of exceptions in C#?:

    @cheong said in How to find "common" types of exceptions in C#?:

    But this ClassName C# property can also got mapped automatically for JSON properties "ClassName", and "className" too.

    And that's wrong, because JSON property can't have alternate names. The schema has no way of specifying that. So make up your mind and only map one of them. You have to make up your mind for serialization anyway.

    I'm describing what happens if the properties are not annotated at all. The JSON deserializer will try multiple tactics to attempt matching a property.

    If it's annotated, then of course things will be easier and more deterministic.



  • @cheong said in How to find "common" types of exceptions in C#?:

    I'm describing what happens if the properties are not annotated at all.

    I know what it does. I'm saying I consider it retarded.



  • @Bulb
    To bad the annotations in go are case sensitive until they aren't. https://go.dev/play/p/_uQErbkUc6m

    But yeah, they are the correct way to go. They just need to be implemented as perfect matches instead of the case insensitive matching go does (and yes, we already had a bug because of that because we had already two properties with names only differing in casing and go selected the wrong field of a struct to serialize to. Just comment out MyData2) and observe it failing to parse the JSON)



  • @turingmachine … actually, they are … Broken:trwtf:

    See, the sane-data was too different, so it did not override the saneData. But if I made it insaneData and InSaneData, the later overrode the former :wtf-whistling:.


  • Trolleybus Mechanic

    @Rhywden said in How to find "common" types of exceptions in C#?:

    @dkf Ah, okay, then. Hrmph. I just wish there was a way to filter for "common" exceptions / error conditions and the "exotic" ones.

    Kind of like:

    try {
       OneOf<T, ServerError, NotFound, ParseInvalid> result = await HttpClient.GetFromJsonAsync<T>(url);
    } catch(Exception e) {
       //deal with more "outlandish" stuff here
    }
    

    Wouldn't a catch when work for this?



  • @GOG said in How to find "common" types of exceptions in C#?:

    @Rhywden said in How to find "common" types of exceptions in C#?:

    @dkf Ah, okay, then. Hrmph. I just wish there was a way to filter for "common" exceptions / error conditions and the "exotic" ones.

    Kind of like:

    try {
       OneOf<T, ServerError, NotFound, ParseInvalid> result = await HttpClient.GetFromJsonAsync<T>(url);
    } catch(Exception e) {
       //deal with more "outlandish" stuff here
    }
    

    Wouldn't a catch when work for this?

    I believe multiple catch blocks with the most restrictive type on top is enough.

    "Case when" is needed only when the exception type is the same and only different in things like error code.


  • Trolleybus Mechanic

    @cheong The question is what one is trying to achieve, and this isn't clear to me.

    Essentially, what I understand @Rhywden to be saying here is that he'd like to handle some number of "common" exceptions one way, and everything else another way. It seems to me that catch when (filtering on Type) might be the simplest way to catch multiple different types of exception in one block, whilst allowing everything else to fall through to the general case.


  • 🚽 Regular

    @GOG said in How to find "common" types of exceptions in C#?:

    Wouldn't a catch when work for this?

    You mean something like...?

    try {
       T result = await HttpClient.GetFromJsonAsync<T>(url);
    } catch(Exception e) when (
                                e is ServerError ||
                                e is NotFound ||
                                e is ParseInvalid){
       //deal with "regular" exceptions
    }
    } catch(Exception e) {
       //deal with more "outlandish" stuff here
    }
    

    It's possible, though definitely 🤨-worthy.

    I like this pattern:

    record GetFromJsonResult
    {
        public record Ok<T>(T t) : GetFromJsonResult;
        public record NullReturned() : GetFromJsonResult;
        public record ServerError(string ErrorMessage) : GetFromJsonResult;
        public record NotFound() : GetFromJsonResult;
        public record ParseInvalid(long? LineNumber, long? BytePositionInLine) : GetFromJsonResult;
        private GetFromJsonResult() { }
    }
    
    async Task<GetFromJsonResult> GetAsync<T>(Uri uri)
    {
        using var httpClient = new HttpClient();
        try {
            var t = await httpClient.GetFromJsonAsync<T>(uri);
            if(t == null){
                return new GetFromJsonResult.NullReturned();
            }
            return new GetFromJsonResult.Ok<T>(t);
        }
        catch(HttpRequestException hre){
            if(HttpStatusCode.NotFound.Equals(hre.StatusCode)){
                return new GetFromJsonResult.NotFound();
            }
            return new GetFromJsonResult.ServerError(hre.Message);
        }
        catch(JsonException jex){
            return new GetFromJsonResult.ParseInvalid(jex.LineNumber, jex.BytePositionInLine);
        }
    }
    
    // Use like this
    
    var result = await GetAsync<Thing>(new Uri("http://127.0.0.1/someurl"));
    switch (result)
    {
        case GetFromJsonResult.NullReturned:
            Console.WriteLine("null was returned");
            break;
        case GetFromJsonResult.Ok<Thing>(Thing t):
            UseThing(t);
            break;
        case GetFromJsonResult.NotFound:
            Console.WriteLine("Not found, yo");
            break;
        case GetFromJsonResult.ParseInvalid pi:
            Console.WriteLine($"Fix your shit at {pi.LineNumber}:{pi.BytePositionInLine}");
            break;
        case GetFromJsonResult.ServerError se:
            Console.WriteLine($"Server says no: {se.ErrorMessage}");
            break;
    }
    

    You can address as many or as few cases in the switch as you want and just lump everything else in the default branch.


  • 🚽 Regular

    ☝ This of course does not address @Rhywden's query at all.

    It's just how I wish the API had been designed. I like sum types / discriminated unions.


  • Trolleybus Mechanic

    @Zecc said in How to find "common" types of exceptions in C#?:

    It's possible, though definitely -worthy.

    Good to know.

    Personally, I can't think of any scenario where I wouldn't want to have specific handling for specific exceptions and a general case for everything else, but...


  • 🚽 Regular

    @GOG I'm in two minds about it myself.

    On the one hand it would look better with multiple catch clauses, one for which exception type.

    On the other hand that might result in code duplication across branches.

    On the third hand you can't have fallthroughs from one branch to the other. *

    So while 🤨-worthy, it might be the best we can do.


    * This made me think of what happens when you try to make a switch statement with a fallthrough when there's pattern matching involved. This happens:

    9fa01d07-a6a2-4d66-838e-e208b046824c-image.png



  • @Zecc said in How to find "common" types of exceptions in C#?:

    On the third hand

    Where did you get a third hand? I'm still stuck with two.


  • Trolleybus Mechanic

    @Bulb said in How to find "common" types of exceptions in C#?:

    @Zecc said in How to find "common" types of exceptions in C#?:

    On the third hand

    Where did you get a third hand? I'm still stuck with two.


  • BINNED

    @GOG Made my day!



  • @Bulb said in How to find "common" types of exceptions in C#?:

    @Zecc said in How to find "common" types of exceptions in C#?:

    On the third hand

    Where did you get a third hand? I'm still stuck with two.

    https://www.youtube.com/watch?v=gV0bkFs0cfY
    (CBA to find english dub... if it even exists)


  • 🚽 Regular

    @Bulb said in How to find "common" types of exceptions in C#?:

    Where did you get a third hand? I'm still stuck with two.

    AI image generation. 🍹


  • Discourse touched me in a no-no place

    @GOG Ah yes, the gripping hand.


  • Considered Harmful

    @Zecc said in How to find "common" types of exceptions in C#?:

    You can address as many or as few cases in the switch as you want and just lump everything else in the default branch.

    And if you had a proper notion of enumerated types at the language level, those could be polymorphic behavior of the enum value.


  • 🚽 Regular

    @Gribnit Aka ECZMAScript-like Promise callback hell?


Log in to reply