Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save rakjin/c1b46c3722ab419465ab15a1061e119e to your computer and use it in GitHub Desktop.
Save rakjin/c1b46c3722ab419465ab15a1061e119e to your computer and use it in GitHub Desktop.
Deserialize JavaScript/TypeScript tagged type (discriminated union) with C# and Newtonsoft.Json

TypeScript protocol for a JSON response object (from node.js backend for example):

interface Circle {
  type: "circle";
  radius: number;
}

interface Square {
  type: "square";
  size: number;
}

type Figure = Circle | Square;

interface Response {
  figure?: Figure;
}

Possible response examples:

{ 
  "figure": { 
    "type": "square", 
    "size": 1.24
  }
}
{ 
  "figure": { 
    "type": "circle", 
    "radius": 3.25
  }
}
{ }

C# response deserialization:

using System;
using System.Collections.Generic;

using Newtonsoft.Json;
using Newtonsoft.Json.Linq;


namespace Guess
{
    public sealed class Square
    {
        public String Type { get; set; }

        public Single Size { get; set; }

        public static readonly String ExpectedType = "square";
    }

    public sealed class Circle
    {
        public String Type { get; set; }

        public Single Radius { get; set; }

        public static readonly String ExpectedType = "circle";
    }

    public sealed class Response 
    {
        [JsonProperty("figure")]
        public JToken FigureRaw { get; set; }

        [JsonIgnore]
        public Object Figure
        {
            get
            {
                if (this.TryGetFigureType(out var type))
                {
                    return this.FigureRaw.ToObject(type);
                }
                else 
                {
                    return null;
                }
            }
        }

        private Boolean TryGetFigureType(out Type value) 
        {
            if (this.FigureRaw == null)
            {
                value = null;
                return false;
            }

            var type = this.FigureRaw.Value<String>("type");
            if (type == Square.ExpectedType)
            {
                value = typeof(Square);
                return true;
            }
            else if (type == Circle.ExpectedType)
            {
                value = typeof(Circle);
                return true;
            }
            throw new NotSupportedException($"Not supported figure type: {type}");
        }
    }

    class Program
    {
        static void Main(String[] args)
        {
            var responses = new [] 
            {
                JsonConvert.DeserializeObject<Response>(@"{ ""figure"": { ""type"": ""square"", ""size"": 1.24 } }"),
                JsonConvert.DeserializeObject<Response>(@"{ ""figure"": { ""type"": ""circle"", ""radius"": 3.25 } }"),
                JsonConvert.DeserializeObject<Response>(@"{ }"),
            };

            foreach (var response in responses) 
            {
                var figure = response.Figure;
                switch (figure) 
                {
                    case null:
                        Console.WriteLine("Figure has not been set");
                        break;
                    case Square square: 
                        Console.WriteLine($"Square size: {square.Size}");
                        break;
                    case Circle circle:
                        Console.WriteLine($"Circle radius: {circle.Radius}");
                        break;
                    default:
                        throw new NotSupportedException($"Not supported figure type: {figure.GetType()}");
                }
            }
        }
    }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment