Skip to content

Instantly share code, notes, and snippets.

@vbilopav
Last active February 1, 2023 12:40
Show Gist options
  • Save vbilopav/152d87c689598ace8cb28da3f61e0f4e to your computer and use it in GitHub Desktop.
Save vbilopav/152d87c689598ace8cb28da3f61e0f4e to your computer and use it in GitHub Desktop.
/*
Creates TypeScript models from CSharp models
- TypeScript model is set by config key TsModelsPath
- CSharp models namespace is set by config key ModelNamespace
1) Add to your Program.cs:
if (ModelBuilder.Build(args))
{
return;
}
2) Call with: "dotnet run -- build-models"
*/
using System.Reflection;
using System.Text;
namespace MyNamespace.Scripts;
public static class ModelBuilder
{
private static bool IsNullable(this Type type) => type == typeof(string) || type.IsClass
? true
: type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>);
private static bool IsDict(this Type type) => type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Dictionary<,>);
private static bool IsList(this Type type) => type.IsGenericType && type.GetGenericTypeDefinition() == typeof(List<>);
private static string ToTsType(this Type? type) => type switch
{
Type t when t == typeof(string) => "string",
Type t when t == typeof(List<string>) => "string[]",
Type t when t == typeof(int) || t == typeof(int?) => "number",
Type t when t == typeof(bool) || t == typeof(bool?) => "boolean",
Type t when t.IsList() => $"I{t.GenericTypeArguments?.FirstOrDefault()?.ToTsType()}[]",
Type t when t.IsDict() => $"Record<{t.GenericTypeArguments?.FirstOrDefault()?.ToTsType()}, {t.GenericTypeArguments?.LastOrDefault()?.ToTsType()}>",
Type t when t.IsClass => $"I{t.Name}",
_ => "any"
};
public static bool Build(string[] args)
{
if (!(args.Length == 1 && args[0] == "build-models"))
{
return false;
}
var config = new ConfigurationBuilder()
.AddJsonFile("appsettings.json", false, false)
.AddJsonFile("appsettings.Development.json", false, false)
.Build();
var filePath = config.GetValue<string>("TsModelsPath");
if (filePath is null)
{
Console.WriteLine("ERROR: ConfigModelsPath is not set.");
return true;
}
var modelNamespace = config.GetValue<string>("ModelNamespace");
if (filePath is null)
{
Console.WriteLine("ERROR: ModelNamespace is not set.");
return true;
}
StringBuilder sb = new();
sb.AppendLine("/*auto generated*/");
foreach (var modelClass in Assembly.Load(modelNamespace?.Split('.')[0] ?? "")
.GetTypes()
.Where(t => string.Equals(t.Namespace, modelNamespace, StringComparison.Ordinal)) ?? Enumerable.Empty<Type>())
{
sb.AppendLine();
sb.AppendLine($"interface I{modelClass.Name} {{");
foreach (var prop in modelClass.GetProperties())
{
var type = prop.PropertyType.ToTsType();
var nullable = prop.PropertyType.IsNullable();
var name = string.Concat(prop.Name[0].ToString().ToLower(), prop.Name[1..]);
sb.AppendLine($" {name}{(nullable ? "?" : "")}: {type};");
}
sb.AppendLine("}");
}
File.WriteAllText(filePath, sb.ToString());
Console.WriteLine($"{filePath} written successfully");
return true;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment