I’m trying to read from a data file where everything is stored in arrays, and transfer that to the equivalent Javascript object.
What I want to be able to do with typescript is create a system that automatically reads the arrays, and stores them in corresponding object, but also is able to verify that the types are correct in order to avoid hard to debug errors.
What I’d like to be able to do is something like the following
//Template that will specify types of Typescript, but also allow me to use the keys with javascript
const SaveDataTemplate = {
id: Number(),
name: String(),
someBool: Boolean(),
}
//Type to allow for type-checking the save data input/output
type tSaveData = GetKeyTypesFrom<SaveDataTemplate>; //Theoretically would return constant array type of [number, string, boolean];
//Takes in array from save data and returns key/value object
function fromSaveData(data: tSaveData): typeof SaveDataTemplate {
const returnObj = {};
let idx = 0;
for (const key in SaveDataTemplate){
returnObj[key] = data[idx++];
}
return returnObj as any;
}
//Takes in object which contains keys which are in SaveDataTemplate and returns array in specific format
function toSaveData(data: typeof SaveDataTemplate): tSaveData {
const returnArr = [];
for (const key in data){
returnArr.push(data[key]);
}
return returnArr as any;
}
In a language like C++ Struct
s would make this easy, but since Jvascript doesn’t have those it’s turning out to be pretty difficult to find a workaround.
2
Answers
For better or worse, TypeScript does not keep track of the order of keys in object types. This request has been asked and declined multiple times, see microsoft/TypeScript#39701 for example. Indeed this was even introduced as an April Fool’s Day feature announcement at microsoft/TypeScript#58019. The
keyof
type operator produces an union of key types; similarly you cannot get "the order" of a union as a tuple. There are ways to force TypeScript to divulge its internal order representation of a union, but those are not guaranteed to be what you expect. See How to transform union type to tuple type. This is not a feature of TypeScript and will almost certainly never be.You really don’t want key order to matter, in general, otherwise
{a: string, b: number}
and{b: number, a: string}
would be different types, and in the vast majority of cases you don’t want to keep track of when a property was added to an object, or where in the object literal the key appears.So TypeScript does not know how to take
SaveDataTemplate
and produce[number, string, boolean]
versus, say,[boolean, number, string]
or any other permuation. Similarly it cannot be sure what thefor...in
loopfor (const key in SaveDataTemplate)
is going to do. Any approach which relies on key ordering is going to need explicit assertions to tell TypeScript what to expect, and this is fragile. YourtoSaveData()
function, for example, will do unpredictable things, since there’s no way for TypeScript to notice that you’ve passed in{id:0, name:"", someBool:true}
and not{someBool:true, id:0, name:""}
. I’d say you should avoid such an approach altogether.Instead, you might consider changing your data structure to an inherently ordered type. For example, a
const
-asserted array literal of key-value pairs:This is enough to construct both a tuple of value types, as desired:
and the corresponding object type of your original data structure (what you’d get if you used
Object.fromEntries()
on the array of key-value pairs:(Note that I changed the word "key" to "value" in my type names, since
[number, string, boolean]
is a tuple of property values, not keys.)And you can use the array of key-value pairs to implement your functions in a way that’s guaranteed to work no matter what order the object keys happen to be:
In both cases we’re looping over the "key" elements in the
saveDataTemplate
array. Those will always be in the same order no matter what. Specifically intoSaveData()
, the inputdata
object is not required to have keys in any particular order. As long as it’s a validTSaveDataObject
, the function will serialize the object consistently:Playground link to code
You could try setting up a basic type map to use when inferring the field type from a template. This is similar to how Zod infers the type of a schema. In your case, the template is a schema.
You can remove the
readonly
from the fields by prefixing-readonly
. This will make the object mutable.Here are two separate use cases: