Skip to main content

How To Save And Load

Requirements

Save and load requires that your project uses some form of authentication when deployed. This might be done via a plugin such as Auth0 or through an integration with your existing login solution with JSON Web Tokens.

JSON

To save a design, we convert it to a JSON object, which can be sent to the server and stored in a database. For this to work, each component must have a function called generateJSON that creates a JSON representation of its current configuration. Luckily, by extending the STUDIO.BaseComponent class, we get this for free.

To load a design, we need to parse the JSON that was saved to the database. To accomplish this, each component must have a static fromJSON function. Just like with generateJSON, this is added by extending the STUDIO.BaseComponent class.

Handling Breaking Changes

Let say we have a chair component that comes in red or black.

interface Properties {
color: 'red' | 'black'
}

class Component extends STUDIO.BaseComponent<Properties> {
protected properties: Properties

constructor() {
this.setProperties({ color: 'red' })
}
}

We deploy this project and our customers use it and save their configurations successfully. After a few months, our marketing department has decided that we should no longer produce any black chairs. They have also decided that any chairs that were saved as black should now be orange instead if loaded.

To deal with this mess, we implement custom generateJSON and fromJSON functions.

interface Properties {
color: 'red' | 'orange'
}

class Component extends STUDIO.BaseComponent<Properties> {
protected properties: Properties

constructor() {
this.setProperties({ color: 'red' })
}

generateJSON (componentIdMapping: ComponentIdMapping[]): ComponentJSON<Properties> {
return {
v: 2,
ch: this.componentHandler.generateJSON(componentIdMapping),
p: this.properties,
}
}

static fromJSON (json: ComponentJSON<Properties>, componentIdMapping: ComponentIdMapping[]) {
const component = new this()

if (json.v === undefined || json.v < 2) {
/**
* Chairs before version 2 which were black should now be orange instead.
* See https://docs.mycompany.com/design-specifications/123
*/
if (json.p.color === 'black') {
json.p.color = 'orange'
}
}

component.properties = json.p

if (json.ch) {
component.componentHandler = ComponentHandler.fromJSON(json.ch, componentIdMapping)
}

return component
}
}

By applying this pattern we can gather all the breaking changes in one place, which improves readability. By catching the change as early as possible when loading the saved design, we can also keep the rest of the code clean from checking values that were valid before but that should no longer be used.

Next time that we have to change something like this in the chair component, we simply increase the version v to 3 and add another if-statement.

generateJSON (componentIdMapping: ComponentIdMapping[]): ComponentJSON<Properties> {
return {
v: 3,
ch: this.componentHandler.generateJSON(componentIdMapping),
p: this.properties,
}
}

static fromJSON (json: ComponentJSON<Properties>, componentIdMapping: ComponentIdMapping[]) {
const component = new this()

if (json.v === undefined || json.v < 3) {
// Handle versions lower than 3 here
}

if (json.v === undefined || json.v < 2) {
/**
* Chairs before version 2 which were black should now be orange instead.
* See https://docs.mycompany.com/design-specifications/123
*/
if (json.p.color === 'black') {
json.p.color = 'orange'
}
}

component.properties = json.p

if (json.ch) {
component.componentHandler = ComponentHandler.fromJSON(json.ch, componentIdMapping)
}

return component
}