Skip to content

Instantly share code, notes, and snippets.

@xiaomi7732
Last active January 6, 2023 22:58
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save xiaomi7732/834f28034c64af91492392b5b7d36fa5 to your computer and use it in GitHub Desktop.
Save xiaomi7732/834f28034c64af91492392b5b7d36fa5 to your computer and use it in GitHub Desktop.
How do I leverage .NET 7 Polymorphic Serialization when building Roshambo API

How did I leverage .NET 7 Polymorphic Serialization when building Roshambo API

Brief about the Roshambo project

It is a simple rock, paper, and scissors game used to study how to build a full RESTful API. You will be able to find the latest code in this repo. BTW, if you like it, give it a star.

To make it fun, I also released a live client at https://roshambo.codewithsaar.com.

The feature I want to build

For any response, I intend to return what are the possible actions in a collection named next. That brings the API closer to fitting into RESTful constraints of the response that contains hypermedia.

For example, the very first GET at / (try it here), this is an example of the response:

{
  "suggestedUserId": {
    "value": "2883385a-7af4-4f41-a69e-640ceee50053"
  },
  "self": {
    "href": "https://roshamboapp.azurewebsites.net/",
    "rel": "self",
    "method": "get",
    "key": null
  },
  "next": [
    {
      "href": "https://roshamboapp.azurewebsites.net/players/{uid}",
      "rel": "ready",
      "method": "get",
    }
  ]
}

Pay attention to the next object, there is one action, and it has 3 properties: href, rel, and method, and in the model, I used a class to represent it:

public class RelModel
{
    public string Rel { get; init; } = default!;
    public string Href { get; }
    public string Method { get; init; } = HttpMethod.Get.ToString().ToLowerInvariant();
}

It is pretty standard. Now, follow the link to invoke the 2nd GET, (try it here)

The response looks like this:

{
  "next": [
    {
      "href": "https://roshamboapp.azurewebsites.net/rounds/rock",
      "rel": "ready",
      "method": "post",
      "key": "rock"
    },
    {
      "href": "https://roshamboapp.azurewebsites.net/rounds/paper",
      "rel": "ready",
      "method": "post",
      "key": "paper"
    },
    {
      "href": "https://roshamboapp.azurewebsites.net/rounds/scissor",
      "rel": "ready",
      "method": "post",
      "key": "scissor"
    }
  ],
  ...
}

I have 3 actions in the next collection, and there is an additional key property for each action. To represent that, I introduced 2 levels of classes:

public abstract class RelModelWithKey : RelModel // Abstraction of the action with key
{
    public abstract string Key { get; }
}

public class RockAciton : RelModelWithKey    // Concrete action of rock
{
    ...
}

public class PaperAction : RelModelWithKey    // Concrete action of paper
...
public class ScissorAction : RelModelWithKey    // Concrete action of scissor

Now, let's abstract a class for the result. Since I want all the responses to always return an action list, I am willing to introduce a base class:

public class ResponseBase
{
    public RelModel Self { get; init; }
    public IEnumerable<RelModel> Next { get; init; }
}

Here's a problem: in .NET 6 or prior, any action in Next will be serialized using the base class, RelModel in this case, that it won't have Key property for any instance of RelModelWithKey. Probably a way to work around it is to write a custom serializer.

Leverage Polymorphic Serialization in .NET 7

In .NET 7, the serializer will do a type check for the derived class - as long as it is in the list like this:

[JsonDerivedType(typeof(RockAction))]
[JsonDerivedType(typeof(PaperAction))]
[JsonDerivedType(typeof(ScissorAction))]
[JsonDerivedType(typeof(CustomRel))]
public class RelModel
{
...
}

Upon serialization, the serializer will check whether the runtime object matches any known type by JsonDerivedType attribute. If it matches, it will then serialize the object with the matched type than the general base type.

Not only that addressed my problem, it also enables us to put any derived class of RelModel into the Next collection, as long as we mark the derived class with JsonDerivedType attribute!

I think the polymorphic serialization capability makes life easier!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment