Skip to content

Instantly share code, notes, and snippets.

@holly-hacker
Last active November 4, 2020 13:42
Show Gist options
  • Save holly-hacker/d6f904a8977df68e13bcbc5b90c27d9e to your computer and use it in GitHub Desktop.
Save holly-hacker/d6f904a8977df68e13bcbc5b90c27d9e to your computer and use it in GitHub Desktop.
Generates a TypeScript definition file for PokéAPI
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