Last active
November 4, 2020 13:42
-
-
Save holly-hacker/d6f904a8977df68e13bcbc5b90c27d9e to your computer and use it in GitHub Desktop.
Generates a TypeScript definition file for PokéAPI
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Globalization; | |
using System.IO; | |
using System.Linq; | |
using System.Net; | |
using System.Text; | |
using Newtonsoft.Json; | |
using Newtonsoft.Json.Converters; | |
using PokeApiDefinitionGenerator; | |
using QuickType; | |
namespace PokeApiDefinitionGenerator | |
{ | |
internal static class Program | |
{ | |
/// <summary> | |
/// Url template for raw json data. | |
/// </summary> | |
private const string UrlBase = @"https://raw.githubusercontent.com/PokeAPI/pokeapi.co/master/src/docs/{0}.json"; | |
/// <summary> | |
/// Order made to be the same as mudkipme/pokeapi-v2-typescript | |
/// </summary> | |
private static readonly string[] DocEntries = { | |
"resource-lists", | |
"berries", "contests", "encounters", "evolution", "games", "items", "machines", "moves", "locations", "pokemon", | |
"utility" | |
}; | |
private static void Main(string[] args) | |
{ | |
var sb = new StringBuilder(); | |
sb.AppendLine("interface APIResourceURL<T> extends String {}"); | |
sb.AppendLine(); | |
foreach (string url in DocEntries.Select(x => string.Format(UrlBase, x))) { | |
string json = new WebClient().DownloadString(url); | |
var allData = JsonConvert.DeserializeObject<DocData[]>(json, Converter.Settings); | |
foreach (DocData data in allData) { | |
bool isFirst = true; | |
foreach (ResponseModel model in data.ResponseModels) { | |
if (isFirst) { | |
if (!string.IsNullOrWhiteSpace(data.Description)) | |
sb.AppendLine($"/** {data.Description} */"); | |
isFirst = false; | |
} | |
sb.AppendLine($"interface {FixType(model.Name, true)} {{"); | |
foreach (Field field in model.Fields) { | |
if (!string.IsNullOrWhiteSpace(field.Description)) | |
sb.AppendLine($" /** {field.Description} */"); | |
sb.AppendLine($" {field.Name}: {field.Type.ToString()};"); | |
} | |
sb.AppendLine("}"); | |
sb.AppendLine(); | |
} | |
} | |
} | |
Console.WriteLine(sb.ToString()); | |
File.WriteAllText("poke-api.d.ts", sb.ToString()); | |
} | |
public static string FixType(string s, bool allowGeneric = false) | |
{ | |
var apiResourceNames = new [] { "APIResource", "APIResourceList", "NamedAPIResource", "NamedAPIResourceList" }; | |
s = s | |
.Replace('é', 'e') | |
.Replace("integer", "number"); | |
if (s.Contains(" ")) | |
s = s.Replace(" ", string.Empty); | |
if (apiResourceNames.Contains(s) && allowGeneric) | |
s += "<T>"; | |
return s; | |
} | |
} | |
} | |
namespace QuickType | |
{ | |
public class DocData | |
{ | |
[JsonProperty("name")] | |
public string Name { get; set; } | |
[JsonProperty("description")] | |
public string Description { get; set; } | |
[JsonProperty("exampleRequest")] | |
public string ExampleRequest { get; set; } | |
[JsonProperty("exampleResponse")] | |
public object ExampleResponse { get; set; } | |
[JsonProperty("responseModels")] | |
public ResponseModel[] ResponseModels { get; set; } | |
} | |
public class ResponseModel | |
{ | |
[JsonProperty("name")] | |
public string Name { get; set; } | |
[JsonProperty("fields")] | |
public Field[] Fields { get; set; } | |
} | |
public class Field | |
{ | |
[JsonProperty("name")] | |
public string Name { get; set; } | |
[JsonProperty("description")] | |
public string Description { get; set; } | |
[JsonProperty("type")] | |
public TypeUnion Type { get; set; } | |
} | |
public class TypeClass | |
{ | |
[JsonProperty("type")] | |
public string Type { get; set; } | |
[JsonProperty("of")] | |
public TypeUnion Of { get; set; } | |
} | |
public struct TypeUnion | |
{ | |
public string String; | |
public TypeClass TypeClass; | |
public override string ToString() | |
{ | |
if (!(String is null)) | |
return Program.FixType(String); | |
if (!(TypeClass is null)) | |
return TypeClass.Type == "list" | |
? $"{Program.FixType(TypeClass.Of.ToString(), true)}[]" | |
: $"{Program.FixType(TypeClass.Type)}<{Program.FixType(TypeClass.Of.ToString(), true)}>"; | |
throw new Exception("Bad tuple?"); | |
} | |
} | |
internal static class Converter | |
{ | |
public static readonly JsonSerializerSettings Settings = new JsonSerializerSettings | |
{ | |
MetadataPropertyHandling = MetadataPropertyHandling.Ignore, | |
DateParseHandling = DateParseHandling.None, | |
Converters = | |
{ | |
TypeUnionConverter.Singleton, | |
new IsoDateTimeConverter { DateTimeStyles = DateTimeStyles.AssumeUniversal } | |
}, | |
}; | |
} | |
internal class TypeUnionConverter : JsonConverter | |
{ | |
public override bool CanConvert(Type t) => t == typeof(TypeUnion) || t == typeof(TypeUnion?); | |
public override object ReadJson(JsonReader reader, Type t, object existingValue, JsonSerializer serializer) | |
{ | |
switch (reader.TokenType) | |
{ | |
case JsonToken.String: | |
case JsonToken.Date: | |
var stringValue = serializer.Deserialize<string>(reader); | |
return new TypeUnion { String = stringValue }; | |
case JsonToken.StartObject: | |
var objectValue = serializer.Deserialize<TypeClass>(reader); | |
return new TypeUnion { TypeClass = objectValue }; | |
} | |
throw new Exception("Cannot unmarshal type TypeUnion"); | |
} | |
public override void WriteJson(JsonWriter writer, object untypedValue, JsonSerializer serializer) | |
{ | |
var value = (TypeUnion)untypedValue; | |
if (value.String != null) | |
{ | |
serializer.Serialize(writer, value.String); | |
return; | |
} | |
if (value.TypeClass != null) | |
{ | |
serializer.Serialize(writer, value.TypeClass); | |
return; | |
} | |
throw new Exception("Cannot marshal type TypeUnion"); | |
} | |
public static readonly TypeUnionConverter Singleton = new TypeUnionConverter(); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment