builder icon indicating copy to clipboard operation
builder copied to clipboard

[TypeScript] BuilderContent cannot be JSON-serialized

Open jamesarosen opened this issue 2 years ago • 2 comments

Describe the bug The result of builder.get(…).promise() can be serialized to JSON, but when deserialized, the result is no longer a BuilderContent and cannot be used with <BuilderComponent content={…} /> according to TypeScript (though it does work).

The TypeScript error from the latest version of Remix, which uses the serialization and deserialization to improve page-load performance:

Type 'SerializeObject<UndefinedToOptional<Article>>' is not assignable to type 'BuilderContent'.
  Types of property 'variations' are incompatible.
    Type 'SerializeObject<UndefinedToOptional<{ [id: string]: BuilderContentVariation | undefined; }>> | undefined' is not assignable to type '{ [id: string]: BuilderContentVariation | undefined; } | undefined'.
      Type 'SerializeObject<UndefinedToOptional<{ [id: string]: BuilderContentVariation | undefined; }>>' is not assignable to type '{ [id: string]: BuilderContentVariation | undefined; }'.
        'string' index signatures are incompatible.
          Type 'SerializeObject<UndefinedToOptional<BuilderContentVariation>>' is not assignable to type 'BuilderContentVariation'.
            Types of property 'data' are incompatible.
              Type 'SerializeObject<UndefinedToOptional<{ [key: string]: any; blocks?: BuilderElement[] | undefined; inputs?: Input[] | undefined; state?: { [key: string]: any; } | undefined; }>> | undefined' is not assignable to type '{ [key: string]: any; blocks?: BuilderElement[] | undefined; inputs?: Input[] | undefined; state?: { [key: string]: any; } | undefined; } | undefined'.
                Type 'SerializeObject<UndefinedToOptional<{ [key: string]: any; blocks?: BuilderElement[] | undefined; inputs?: Input[] | undefined; state?: { [key: string]: any; } | undefined; }>>' is not assignable to type '{ [key: string]: any; blocks?: BuilderElement[] | undefined; inputs?: Input[] | undefined; state?: { [key: string]: any; } | undefined; }'.
                  Types of property 'blocks' are incompatible.
                    Type 'SerializeObject<UndefinedToOptional<BuilderElement>>[] | undefined' is not assignable to type 'BuilderElement[] | undefined'.
                      Type 'SerializeObject<UndefinedToOptional<BuilderElement>>[]' is not assignable to type 'BuilderElement[]'.
                        Type 'SerializeObject<UndefinedToOptional<BuilderElement>>' is not assignable to type 'BuilderElement'.
                          Types of property 'responsiveStyles' are incompatible.
                            Type 'SerializeObject<UndefinedToOptional<{ large?: Partial<CSSStyleDeclaration> | undefined; medium?: Partial<CSSStyleDeclaration> | undefined; small?: Partial<...> | undefined; xsmall?: Partial<...> | undefined; }>> | undefined' is not assignable to type '{ large?: Partial<CSSStyleDeclaration> | undefined; medium?: Partial<CSSStyleDeclaration> | undefined; small?: Partial<...> | undefined; xsmall?: Partial<...> | undefined; } | undefined'.
                              Type 'SerializeObject<UndefinedToOptional<{ large?: Partial<CSSStyleDeclaration> | undefined; medium?: Partial<CSSStyleDeclaration> | undefined; small?: Partial<...> | undefined; xsmall?: Partial<...> | undefined; }>>' is not assignable to type '{ large?: Partial<CSSStyleDeclaration> | undefined; medium?: Partial<CSSStyleDeclaration> | undefined; small?: Partial<...> | undefined; xsmall?: Partial<...> | undefined; }'.
                                Types of property 'large' are incompatible.
                                  Type 'SerializeObject<UndefinedToOptional<Partial<CSSStyleDeclaration>>> | undefined' is not assignable to type 'Partial<CSSStyleDeclaration> | undefined'.
                                    Type 'SerializeObject<UndefinedToOptional<Partial<CSSStyleDeclaration>>>' is not assignable to type 'Partial<CSSStyleDeclaration>'.
                                      Types of property 'parentRule' are incompatible.
                                        Type 'SerializeObject<UndefinedToOptional<CSSRule>> | null | undefined' is not assignable to type 'CSSRule | null | undefined'.
                                          Type 'SerializeObject<UndefinedToOptional<CSSRule>>' is not assignable to type 'CSSRule'.
                                            Types of property 'parentStyleSheet' are incompatible.
                                              Type 'SerializeObject<UndefinedToOptional<CSSStyleSheet>> | null' is not assignable to type 'CSSStyleSheet | null'.
                                                Type 'SerializeObject<UndefinedToOptional<CSSStyleSheet>>' is missing the following properties from type 'CSSStyleSheet': addRule, deleteRule, insertRule, removeRule, and 2 more.ts(2322)

The "Remix way" is to fetch the Builder content server-side, render it once to HTML there, and then also serialize the data as JSON so the client can rehydrate (without asking Builder for the content again). In serializing to JSON, content loses anything that's a function: addRule, deleteRule, insertRule, removeRule, etc.


**To Reproduce**
Steps to reproduce the behavior:
1. fetch some Builder content via `builder.get(…).promise()`
2. serialize the content to JSON
3. deserialize the content from JSON
4. pass the content to `<BuilderComponent content={…} />`. TypeScript complains about incompatible types.

**Expected behavior**
I should be able to serialize and deserialize Builder content across a network and still safely use it with Builder.

**Additional context**
Using `@remix-run/[email protected]`.

See also [this Builder.io Forum discussion](https://forum.builder.io/t/builder-with-remix/2882/8).

jamesarosen avatar Nov 16 '22 18:11 jamesarosen

Hey @jamesarosen thanks for the heads up and work around in case others run into the issue. We will take note and keep this open until we either have a fix or decide on the recommended approach when dealing with JSON serialization

mrkoreye avatar Nov 23 '22 18:11 mrkoreye

oh this is interesting, because we don't actually have any non-serializable data in the return. It's just that for convenience we use Partial<CSSStyleDeclaration> for typing CSS properties, which has methods in the type declaration it seems

That's interesting that Remix is able to validate if types are JSON serializable. We just need to update our types it seems, maybe a quick fix is just to use Omit<CSSStyleDeclaration, 'addRule' | 'deleteRule| ...>, or an even quicker fix is for us to just use Record<string, string> for the CSS objects

steve8708 avatar Jan 19 '23 18:01 steve8708