-
Notifications
You must be signed in to change notification settings - Fork 643
Description
I have an Objection Model (for example Person) and am sending it from the server to the client. Before I send it, I convert the Model to JSON by calling, person.toJSON()
On the client, it would be valuable to have the proper type information for the JSON object but right now, you'd probably type it as an any type and lose all the type-checking.
As mentioned, the data that is transferred from the server to the client is not an actual instance of the Model. Instead, it is just a plain Javascript object that is returned when I call person.toJSON(). This means that it lacks the instance methods from the Model.
Currently, there does not appear to be a way to get the proper type information after calling person.toJSON().
I've created a proof of concept that can be merged into Objection with a little work but I thought I'd post an issue here first to get some feedback on whether a PR is worth it.
For those interested, here is a dump of the proof of concept code. The code below will show no typescript warnings.
import Knex from "knex"
import { Model } from "objection"
class Pet extends Model {
name!: string
species!: string
}
class Country extends Model {
name!: string
}
class Person extends Model {
name!: string
age?: number
country?: Country
pets!: Pet[]
}
const takesKnex = (_: Knex) => 1
const takesPerson = (_: Person) => 1
const takesMaybePerson = (_?: Person) => 1
const takesCountry = (_: Country) => 1
const takesMaybeCountry = (_?: Country) => 1
const takesPet = (_: Pet) => 1
const takesMaybePet = (_?: Pet) => 1
const takesString = (_: string) => 1
const takesMaybeString = (_?: string) => 1
const takesNumber = (_: number) => 1
const takesMaybeNumber = (_?: number) => 1
type ItemOfArray<TArray> = TArray extends (infer TItem)[] ? TItem : never
type ArrayOfTypedJSONFrom<TArray> = Array<TypedJSONFrom<ItemOfArray<TArray>>>
type TypedJSONFrom<T> = {
[key in keyof T]: T[key] extends Function // remove the instance methods
? never
: T[key] extends Model // properties that are models
? TypedJSONFrom<T[key]>
: T[key] extends Model[] // properties that are array of models
? ArrayOfTypedJSONFrom<T[key]>
: T[key]
}
/**
* Takes an instance of an Objection `Model` and converts it using `toJSON`
* and includes all the typing information.
*
* @param model instance of Objection `Model`
*/
function toTypedJSON<T extends Model>(model: T): TypedJSONFrom<T> {
return model.toJSON() as TypedJSONFrom<T>
}
/**
* You can export the PersonJSON type and use it in the client to add typing
* information to the JSON received by the client.
*/
type PersonJSON = TypedJSONFrom<Person>
// tests
;async () => {
const person = await Person.query().findById(1)
takesPerson(person)
takesKnex(person.$knex()) // this passes
takesString(person.name)
takesMaybeNumber(person.age)
const pet = person.pets[0]
takesPet(pet)
takesString(pet.name)
takesString(pet.species)
const pojo = person.toJSON()
const json = toTypedJSON(person)
takesString(json.name)
takesMaybeNumber(json.age)
// takesKnex(json.$knex()) // this will fail because the methods are removed
const jsonCountry = json.country
takesMaybeCountry(jsonCountry)
const jsonPets = json.pets
takesString(jsonPets[0].name)
takesString(jsonPets[0].species)
takesString(jsonPets[1].name)
takesString(jsonPets[1].species)
}