Skip to content

Instantly share code, notes, and snippets.

@oplanre
Last active April 28, 2024 14:42
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 oplanre/7ba61d6e5d02c29ed7c1bdac826928d2 to your computer and use it in GitHub Desktop.
Save oplanre/7ba61d6e5d02c29ed7c1bdac826928d2 to your computer and use it in GitHub Desktop.
Simple TS interface generator (php impl)
<?php
class TSGen
{
public static function fromJSON(string $json, string $interfaceName): string
{
try {
return self::generateInterface(json_decode($json, true, 512, JSON_THROW_ON_ERROR), $interfaceName);
} catch (JsonException $e) {
throw new InvalidArgumentException("Error parsing JSON: " . $e->getMessage());
}
}
public static function fromArray(array $data, string $interfaceName): string
{
return self::generateInterface($data, $interfaceName);
}
//note that this only works for public properties
public static function fromObject(object $object, string $interfaceName = "", bool $useNamespacedClassName = false, $glue = "_"): string
{
if ($interfaceName !== "")
return self::generateInterface(get_object_vars($object), $interfaceName);
if ((new ReflectionClass($object))->isAnonymous()) {
throw new InvalidArgumentException("Anonymous classes must have an interface name");
}
if (!$useNamespacedClassName) {
$name = self::toCamelCase(strrchr(get_class($object), "\\"));
} else {
$name = self::toCamelCase(str_replace("\\", $glue, get_class($object)));
}
return self::generateInterface(get_object_vars($object), $name);
}
// Implementation details
private static function toCamelCase(string $string): string
{
return lcfirst(str_replace(' ', '', ucwords(str_replace(['-', '_'], ' ', $string))));
}
// Generate TypeScript interface definitions from JSON data
private static function generateInterface(array $data, string $interfaceName): string
{
return "interface $interfaceName " . self::generateInterfaceImpl($data) . ";\n";
}
private static function indent(string $code, int $level = 1): string
{
return implode("\n", array_map(fn($line) => str_repeat(" ", $level) . $line, explode("\n", $code)));
}
private static function generateInterfaceImpl(array $data, int $indent = 0): string
{
$definitions = ["{"];
$indent++;
foreach ($data as $key => $value) {
array_push($definitions, self::indent("$key: " .
match (gettype($value)) {
'array' => array_is_list($value) ? self::guessArrayElementType($value) : self::generateInterfaceImpl($value),
'object' => self::generateInterfaceImpl(get_object_vars($value)),
default => self::getTSType($value)
} . ";", $indent));
}
$definitions[] = "}";
return implode("\n", $definitions);
}
private static function getTSType($value): string
{
return match (gettype($value)) {
'integer', 'double' => 'number',
'boolean' => 'boolean',
'string' => 'string',
'array' => self::guessArrayElementType($value),
'NULL' => 'null',
default => 'any'
};
}
private static function guessArrayElementType(array $data): string
{
$types = [];
foreach ($data as $value) {
$types[] = self::getTSType($value);
}
return "Array<" . implode(' | ', array_unique($types)) . ">";
}
}
<?php
$usage = [
"json" => '{
"name": "John",
"age": 30,
"isStudent": true,
"address": {
"street": "123 Main St",
"city": "Anytown"
},
"emails": [
"john@example.com",
"john.doe@example.com"
]
}',
"array" => [
"name" => "John",
"age" => 30,
"isStudent" => true,
"address" => [
"street" => "123 Main St",
"city" => "Anytown"
],
"emails" => [
"john@example.com",
"john.doe@example.com"
]
],
"object" => fn(array $vars) => new readonly class ($vars) {
public string $name;
public int $age;
public bool $isStudent;
public array $address;
public array $emails;
public function __construct(array $vars)
{
foreach ($vars as $key => $value) {
$this->$key = $value;
}
}
}
];
echo TSGen::fromArray($usage['array'], 'Person');
try {
echo TSGen::fromJSON($usage['json'], 'Person');
} catch (InvalidArgumentException $e) {
echo "Error: " . $e->getMessage();
}
try {
echo TSGen::fromObject($usage['object']((array)$usage['array']), 'Person');
} catch (InvalidArgumentException $e) {
echo "Error: " . $e->getMessage();
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment