Given an object like this:
const obj = {
"a64multi": {
"label": "Foo",
"type": "video",
"frameLevelMultithreading": true,
"sliceLevelMultithreading": true,
"experimental": true,
"supportsDrawHorizontalBand": true,
"supportsDirectRenderingMethod1": true
},
"a64multi5": {
"label": "Multicolor charset for Commodore 64, extended with 5th color (colram) (codec a64_multi5)",
"supportsDirectRenderingMethod1": true
}
}
How can you determine the TypeScript type definition from it programmatically? That is, how can you generate the TypeScript source code that defines this object’s type?
I know about typeof obj to get the type of the object using TypeScript, but how do you implement that? I would like to take an arbitrary JavaScript Object type (key/value pairs), and figure out the TypeScript definition of the values that the keys can take. So in this case it would be:
type ObjType =
| {
label: string
type: string
frameLevelMultithreading: boolean
sliceLevelMultithreading: boolean
experimental: boolean
supportsDrawHorizontalBand: boolean
supportsDirectRenderingMethod1: boolean
}
| {
label: string
supportsDirectRenderingMethod1: boolean
}
My nth guess at this would be to basically build up a string for all the keys like this:
console.log(generateHashTypeScriptType('ExampleType', {
"a64multi": {
"label": "Foo",
"type": "video",
"frameLevelMultithreading": true,
"sliceLevelMultithreading": true,
"experimental": true,
"supportsDrawHorizontalBand": true,
"supportsDirectRenderingMethod1": true
},
"a64multi5": {
"label": "Multicolor charset for Commodore 64, extended with 5th color (colram) (codec a64_multi5)",
"supportsDirectRenderingMethod1": true
}
}))
function generateHashTypeScriptType(typeName, obj) {
const types = {}
for (const name in obj) {
const type = determineType(obj[name])
types[JSON.stringify(type)] = true
}
const finalTypes = Object.keys(types).map(string => JSON.parse(string))
return generateTypeScriptType(typeName, finalTypes);
}
function generateTypeScriptType(name, types) {
const text = []
text.push(`export type ${name} = `)
types.forEach(type => {
text.push(` |`)
const lines = generateTypeScriptObjectType(type)
text[text.length - 1] += lines.shift()
lines.forEach(line => {
text.push(` ${line}`)
})
})
return text.join('n')
}
function generateTypeScriptObjectType(type) {
const text = []
text.push(` {`)
for (const name in type) {
const val = type[name]
switch (val.type) {
case 'string':
text.push(` ${name}: string`)
break
case 'number':
text.push(` ${name}: number`)
break
case 'boolean':
text.push(` ${name}: boolean`)
break
case 'object':
text.push(` ${name}:`)
const lines = generateTypeScriptObjectType(val.children)
text[text.length - 1] += lines.shift()
lines.forEach(line => {
text.push(` ${line}`)
})
break
}
}
text.push(`}`)
return text
}
function determineType(obj) {
const out = {}
for (const name in obj) {
const val = obj[name]
if (typeof val === 'string') {
out[name] = {
type: 'string'
}
} else if (typeof val === 'number') {
out[name] = {
type: 'number'
}
} else if (typeof val === 'boolean') {
out[name] = {
type: 'boolean'
}
} else if (val && typeof val === 'object') {
out[name] = {
type: 'object',
children: determineType(obj)
}
}
}
return jsonSort(out)
}
// https://github.com/DawnImpulse/json-keys-sort/blob/master/index.js
function typeOf(data) {
const objectConstructor = {}.constructor
const arrayConstructor = [].constructor
const stringConstructor = 'test'.constructor
if (data && data !== null && data.constructor === objectConstructor) {
return 'OBJECT'
} else if (data && data !== null && data.constructor === arrayConstructor) {
return 'ARRAY'
} else if (data && data !== null && data.constructor === stringConstructor) {
return 'STRING'
} else {
return ''
}
}
function jsonSort(data, sort) {
if (typeOf(data) === 'ARRAY') {
let newData = []
for (let w = 0; w < data.length; w++) {
let d = data[w]
if (typeOf(d) === 'OBJECT' || typeOf(d) === 'ARRAY')
newData.push(jsonSort(d, sort))
else {
newData.push(d)
}
}
return newData
} else if (typeOf(data) === 'OBJECT') {
let newKeys = [],
keys,
newData = {}
if (sort === undefined)
sort = true
keys = Object.keys(data).sort()
if (!sort) {
for (let i = keys.length - 1; i >= 0; i--)
newKeys.push(keys[i])
keys = newKeys
}
for (let j = 0; j < keys.length; j++) {
let key = keys[j]
if (typeOf(data[key]) === 'OBJECT')
newData[key] = jsonSort(data[key], sort)
else if (typeOf(data[key]) === 'ARRAY') {
newData[key] = []
for (let k = 0; k < data[key].length; k++) {
let d = data[key][k]
if (typeOf(d) === 'OBJECT' || typeOf(d) === 'ARRAY')
newData[key].push(jsonSort(data[key][k], sort))
else
newData[key].push(data[key][k])
}
} else
newData[key] = data[key]
}
return newData
} else
throw new Error('must be an object/array')
}
See the console.log output to see what it does. The second half of the code is from json-keys-sort.
Is there a better or more optimal way of accomplishing this? Is there a library for this already? Can I do it without having to write any code other than using some API?