Skip to content

Instantly share code, notes, and snippets.

@romanbsd
Last active August 5, 2023 02:38
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 romanbsd/da181151170e396e8e36a6576f045aa2 to your computer and use it in GitHub Desktop.
Save romanbsd/da181151170e396e8e36a6576f045aa2 to your computer and use it in GitHub Desktop.
#!/usr/bin/env python3
import json
import sys
import re
TYPES = {
'boolean': 'bool',
'integer': 'int',
'string': 'string',
'object': 'object',
'array': 'array',
'null': 'string'
}
def get_obj_def(j):
if j['type'] == 'object':
return j
elif j['type'] == 'array' and 'items' in j:
return j['items']
def get_type(j):
typ = j['type']
if type(typ) == list:
return next(filter(lambda x: x != 'null', typ))
return TYPES[typ]
def capitalize(s):
return re.sub('_([a-z])', lambda x: x[1].capitalize(), s.capitalize())
class Table:
def __init__(self, name, props, required):
self.name, self.required = name, required
self.fields = []
self.build(props)
def build(self, props):
for k, v in props.items():
if 'anyOf' in v:
# FIXME
print('not adding', k, v, file=sys.stderr)
continue
if re.match('^\\d', k):
print('key cannot start with a digit: ', k, file=sys.stderr)
continue
is_array = False
if v['type'] == 'array':
is_array = True
if 'items' not in v or 'type' not in v['items']:
continue
typ = v['items']['type']
if typ == 'object':
typ = capitalize(re.sub('s$', '', k))
else:
typ = capitalize(k) if v['type'] == 'object' else get_type(v)
required = k in self.required
self.add_field(Field(k, typ, is_array=is_array, required=required))
def add_field(self, field):
self.fields.append(field)
def __str__(self):
fields = "\n".join([str(field) for field in self.fields])
return f'table {self.name} {{\n{fields}\n}}\n'
class Field:
def __init__(self, name, type, is_array=False, required=False):
self.name, self.type, self.is_array = name, type, is_array
self.required = required and type not in ['bool', 'int']
def __str__(self) -> str:
kind = f'[{self.type}]' if self.is_array else self.type
required = ' (required)' if self.required else ''
return f' {self.name}: {kind}{required};'
class Schema:
def __init__(self, j, namespace) -> None:
self.namespace = namespace
obj = get_obj_def(j)
self.tables = [Table('Root', obj['properties'], obj['required'])]
self.build(j)
def build(self, j):
if j['type'] not in ['object', 'array']:
raise Exception('unsupported')
self.add_tables(j)
def add_tables(self, j):
obj_def = get_obj_def(j)
if obj_def is None or 'properties' not in obj_def:
return
props = obj_def['properties']
if props is None:
return
for key, val in props.items():
if 'anyOf' in val:
# FIXME
print('anyOf is unsupported, key: ', key, file=sys.stderr)
continue
typ = get_type(val)
name = key.capitalize()
name = re.sub('_([a-z])', lambda x: x[1].capitalize(), name)
if typ == 'object':
self.add_table(name, val)
elif typ == 'array' and 'items' in val and val['items']['type'] == 'object':
self.add_table(re.sub('s$', '', name), val['items'])
else:
continue
self.add_tables(val)
def add_table(self, name, obj_def):
if re.match('^\\d', name):
return
if name in [table.name for table in self.tables]:
return
table = Table(name, obj_def['properties'], obj_def['required'])
self.tables.append(table)
def __str__(self) -> str:
tables = "\n".join([str(table) for table in self.tables])
return f'namespace {self.namespace};\n\n{tables}\nroot_type {self.tables[0].name};'
if __name__ == '__main__':
with open(sys.argv[1]) as f:
j = json.load(f)
namespace = sys.argv[2] if len(sys.argv) > 2 else 'com.example'
schema = Schema(j, namespace)
print(schema)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment