Last active
May 28, 2024 12:22
-
-
Save oplanre/7ba61d6e5d02c29ed7c1bdac826928d2 to your computer and use it in GitHub Desktop.
Simple TS interface generator (php impl)
This file contains hidden or 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
<?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)) . ">"; | |
} | |
} |
This file contains hidden or 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
<?php | |
$usage = [ | |
"json" => '{ | |
"name": "John", | |
"age": 30, | |
"isStudent": true, | |
"address": { | |
"street": "123 Main St", | |
"city": "Anytown" | |
}, | |
"emails": [ | |
"[email protected]", | |
"[email protected]" | |
] | |
}', | |
"array" => [ | |
"name" => "John", | |
"age" => 30, | |
"isStudent" => true, | |
"address" => [ | |
"street" => "123 Main St", | |
"city" => "Anytown" | |
], | |
"emails" => [ | |
"[email protected]", | |
"[email protected]" | |
] | |
], | |
"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