Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Parsing (many) JSON different objects to C# classes. Is strongly typed better?

I have been working on a client - server project. The server side implemented on PHP. The client implemented on C#. The websocket is used for connection between them.

So, here is the problem. Client will make a request. Json is in use for sending objects and validating against the schema. The request MUST HAVE it's name and MAY contain args. Args are like associative array (key => value).

Server will give a response. Response MAY contain args, objects, array of objects. For example, client sends a request like:

    {
    "name": "AuthenticationRequest",
    "username": "root",
    "password": "password",
    "etc": "etc"
    }

For this, server will reply with an AuthSuccess or AuthFailed response like:

    {
    "name": "AuthFailed",
    "args": [
        {
        "reason": "Reason text"
        }]
    }

If response is AuthSuccess, client will send a requst of who is online. Server must send an array of users.

And so on. So the problem is, how to store those responses on a client side. I mean, the way of creating a new object for each response type is insane. They will be hundreds of request types, and each of them requires it's own response. And any changing in structure of request will be very very hard...

Need some kind of pattern or trick. I know it's kind of a noob way... But if anyone has a better idea of implementing request/response structure, please tell it.

Best regards!

like image 649
NightShifter42 Avatar asked Sep 27 '22 20:09

NightShifter42


2 Answers

I'd definitely go with a new class for each request type. Yes, you may need to write a lot of code but it'll be safer. The point (to me) is who will write this code?. Let's read this answer to the end (or directly jump to last suggested option).

In these examples I'll use Dictionary<string, string> for generic objects but you may/should use a proper class (which doesn't expose dictionary), arrays, generic enumerations or whatever you'll feel comfortable with.

1. (Almost) Strongly Typed Classes

Each request has its own strongly typed class, for example:

abstract class Request {
    protected Request(string name) {
        Name = name;
    }

    public string Name { get; private set; }
    public Dictionary<string, string> Args { get; set; }
}

sealed class AuthenticationRequest : Request
{
    public AuthenticationRequest() : base("AuthenticationRequest") {
    }

    public string UserName { get; set; }
    public string Password { get; set; }
}

Note that you may switch to a full typed approach also dropping Dictionary for Args in favor of typed classes.

Pros

What you saw as a drawback (changes are harder) is IMO a big benefit. If you change anything server-side then your request will fail because properties won't match. No subtle bugs where fields are left uninitialized because of typos in strings.

It's strongly typed then your C# code is easier to maintain, you have compile-time checks (both for names and types).

Refactoring is easier because IDE can do it for you, no need to blind search and replace raw strings.

It's easy to implement complex types, your arguments aren't limited to plain string (it may not be an issue now but you may require it later).

Cons

You have more code to write at very beginning (however class hierarchy will also help you to spot out dependencies and similarities).

2. Mixed Approach

Common parameters (name and arguments) are typed but everything else is stored in a dictionary.

sealed class Request {
    public string Name { get; set; }
    public Dictionary<string, string> Args { get; set; }
    public Dictionary<string, string> Properties { get; set; }
}

With a mixed approach you keep some benefits of typed classes but you don't have to define each request type.

Pros

It's faster to implement than a almost/full typed approach.

You have some degree of compile-time checks.

You can reuse all code (I'd suppose your Request class will be also reused for Response class and if you move your helper methods - such as GetInt32() - to a base class then you'll write code once).

Cons

It's unsafe, wrong types (for example you retrieve an integer from a string property) aren't detected until error actually occurs at run-time.

Changes won't break compilation: if you change property name then you have to manually search each place you used that property. Automatic refactoring won't work. This may cause bugs pretty hard to detect.

Your code will be polluted with string constants (yes, you may define const string fields) and casts.

It's hard to use complex types for your arguments and you're limited to string values (or types that can be easily serialized/converted to a plain string).

3. Dynamic

Dynamic objects let you define an object and access it properties/methods as a typed class but they will be actually dynamically resolved at run-time.

dynamic request = new ExpandoObject();
request.Name = "AuthenticationRequest";
request.UserName = "test";

Note that you may also have this easy to use syntax:

dynamic request = new {
    Name = "AuthenticationRequest",
    UserName = "test"
};

Pros

If you add a property to your schema you don't need to update your code if you don't use it.

It's little bit more safe than an untyped approach. For example if request is filled with:

request.UserName = "test";

If you wrongly write this:

Console.WriteLine(request.User);

You will have a run-time error and you still have some basic type checking/conversion.

Code is little bit more readable than completely untyped approach.

It's easy and possible to have complex types.

Cons

Even if code is little bit more readable than completely untyped approach you still can't use refactoring features of your IDE and you almost don't have compile-time checks.

If you change a property name or structure in your schema and you forget to update your code (somewhere) you will have an error only at run-time when it'll happen you use it.

4. Auto-generated Strongly Typed Classes

Last but best...so far we did forget an important thing: JSON has schema with which it can be validatd (see json-schema.org).

How it can be useful? Your fully typed classes can be generated from that schema, let's take a look to JSON schema to POCO. If you don't have/don't want to use a schema you still can generate classes from JSON examples: take a look to JSON C# Class Generator project.

Just create one example (or schema) for each request/response and use a custom code generator/build task to build C# classes from that, something like this (see also MSDN about custom build tools):

Cvent.SchemaToPoco.Console.exe -s %(FullPath) -o .\%(Filename).cs -n CsClient.Model

Pro

All the pros of above solutions.

Cons

Nothing I can think about...

like image 109
Adriano Repetti Avatar answered Sep 30 '22 08:09

Adriano Repetti


Why is it a problem to create a class for each kind of Request / Response? If you have hundreds of different kinds of Requests and Responses, you might want to try and categorize them better.

I would argue there are common patterns across your requests or responses. Eg. a FailureResponse might always contain some status information and maybe an UserData-object (which could be anything depending on the use-case). This can be applied to other categories likewise (eg. SuccessResponse).

like image 22
Jonathan Avatar answered Sep 30 '22 06:09

Jonathan