[66696] Improve deserialization and object creation#271
Conversation
Created two new "base" classes called `model` and `model-collection`. These two classes provide a way for creating models that are not the typical Nylas API models (Event, Calendar, etc.) but will still require similar functionality. Also not all models are "RESTful", `Contact.Email` for example does not.
We are moving on from only supporting instantiating objects from JSON to instantiating objects from properties formatted via camelCase
A followup to the previous commit. Now we can pass in a properties object to properly instantiate an object. The typing is also now fixed as now required properties are no longer optional. We have set them to a default value to allow the users to choose how to create an object - either via passing in a properties object or by setting each field manually.
Setting a default property value will always set that default value after the constructor is executed, meaning if we deserialize in the constructor, the desearilized value will be reset. We tackle this by setting default values for strict properties if no `props` object is passed in.
This method makes it impossible to provide hinting to the user when they want to create a new object. A user should be using `new Model()` instead of using `Nylas.model.build()`.
|
This pull request has been linked to Clubhouse Story #66696: Improve Deserialization. |
|
|
||
| constructor(connection: NylasConnection, props?: CalendarProperties) { | ||
| super(connection, props); | ||
| if(!props) { |
There was a problem hiding this comment.
We can do it this way, or we can make CalendarProperties less strict allowing users to pass in as many or as little initial properties and basically do this.name = props?.name ? props.name : ""; for every required property. (This would be done to all similar models).
|
|
||
| export default class Calendar extends RestfulModel | ||
| implements CalendarProperties { | ||
| name!: string; |
There was a problem hiding this comment.
I added bang (!) as a modifier because we guarantee to set these values somehow -- either via the props that are sent in (which contain the strict properties) or if no props are sent in via setting default values.
|
Ignore merge conflicts for now please! The merge resolution will occur after #267 is merged. Jest is reporting all tests passing and linter + prettier was run on this project. |
philrenaud
left a comment
There was a problem hiding this comment.
Good explanation; this is a nice change!
…tafarashed/ch66696/improve-deserialization
|
|
||
| constructor(props?: IMAddressProperties) { | ||
| super(props); | ||
| if (!props) { |
There was a problem hiding this comment.
Can we just initialize the props with '' instead of having this check?
There was a problem hiding this comment.
Unfortunately, if we initialize props with anything other than an object of type IMAddressProperties (object with type and imAddress set) because of the strictness on those properties. The only way to do it like you're suggesting is to do something like constructor(props: IMAddressProperties = {type: '', imAddress: ''}) {
Alternatively, we can make all of these interfaces less strict allowing users to pass in as many or as little initial properties and in the do something like this.type = props?.type ? props.type : ""; for every required property.
There was a problem hiding this comment.
I have re-worked the props to match what we talked about earlier. The properties now have default values without any extra checks.
Have to call `this.initAttributes(props);` instead of having it in the super class due to Typescript design: https://stackoverflow.com/questions/43595943/why-are-derived-class-property-values-not-seen-in-the-base-class-constructor
Instead of having just a type, this will allow us to move `parseContacts` into the class
| this.calendarId = ''; | ||
| this.when = new When(); | ||
| } | ||
| this.initAttributes(props); |
There was a problem hiding this comment.
The reason why all the classes have this initAttributes call instead of having it in the super class constructor (as it was earlier) is due to how TypeScript is designed in regards to how a child class is initialized. More info can be found here (thanks to @ozsivanov for finding this!): https://stackoverflow.com/questions/43595943/why-are-derived-class-property-values-not-seen-in-the-base-class-constructor
Remove snake_case support and create a new model to better support free-busy
This PR is a big one, it's aimed at two things -- improving how we deserialize objects while improving how users can create objects using the SDK.
Now, each
Modeltype has an accompanying interface that represents an object of parameters that theModelwill use, as well as the proper strictness dictated by what values will always be present in any scenario (GET, POST, PUT). In the constructor of each model, we now specify that we can take in an optionalpropsproperty of the Model interface's type. This helps to hint the user as to what properties they must pass in during the initialization of the object. This also helps us with the strictness of the typing in each model as now we properly outline what properties are needed at a bare minimum.This
propsmethod property is left as optional because it allows some flexibility to the SDK user to just instantiate a type of object then either:If taking this route, we initialize all the required types to a default value in order not to upset Typescript's strictness requirements.
With this, we also deprecate the
Nylas.[model].build()method as it provides a way for the user to bypass the constructor typing requirements and pass in whatever object they want. It also does not provide any type hinting to the user when trying to initialize the object, rendering this work useless. Here's an example of the before and after:License
I confirm that this contribution is made under the terms of the MIT license and that I have the authority necessary to make this contribution on behalf of its copyright owner.