Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

type-fest@3 - How to declare optional properties in JsonObject? #471

Open
peterjuras opened this issue Sep 23, 2022 · 9 comments
Open

type-fest@3 - How to declare optional properties in JsonObject? #471

peterjuras opened this issue Sep 23, 2022 · 9 comments
Assignees
Labels
bug Something isn't working

Comments

@peterjuras
Copy link

peterjuras commented Sep 23, 2022

After the update to version 3.0.0, it is no longer possible to have optional properties on a JsonObject:

import { JsonObject } from "type-fest";

interface MyObject extends JsonObject {
  optionalProp?: string
}

Gives a TypeError:

(property) MyObject.optionalProp?: string | undefined
Property 'optionalProp' of type 'string | undefined' is not assignable to 'string' index type '((string | number | boolean | JsonObject | JsonArray) & (string | number | boolean | JsonObject | JsonArray | undefined)) | null'.ts(2411)

How should this type be used correctly? The code above worked fine with version 2.x.

TypeScript Playground

Upvote & Fund

  • We're using Polar.sh so you can upvote and help fund this issue.
  • The funding will be given to active contributors.
  • Thank you in advance for helping prioritize & fund our backlog.
Fund with Polar
@sindresorhus
Copy link
Owner

Probably related to #465.

// @skarab42

@skarab42 skarab42 self-assigned this Sep 29, 2022
@skarab42 skarab42 added the bug Something isn't working label Sep 29, 2022
@skarab42
Copy link
Collaborator

Unfortunately I can't find (yet) a way to deny the undefined value alone and at the same time allow optional properties with exactOptionalPropertyTypes set to false when extending interface.

A first workaround is to use a type rather than an interface. Playground

type MyObject = JsonObject & {
  optionalProp?: string;
}

A second workaround is to set exactOptionalPropertyTypes to true (Playground).

If none of the above solutions are suitable, a generic type such as ToJsonObject<MyObject> can be provided.

@peterjuras
Copy link
Author

Can you mention in which usecases undefined values are a problem for JsonObjects?

I use this type to describe configuration files, where some values might be missing but are generally valid. The core tooling like JSON.stringify also has no problem with undefined values - it just omits them.

So I'm wondering why this change was made in the first place. The JsonObject type was useful for me, since it described config files well (and didn't allow e.g. Dates or Functions). Now, I can also skip using the type since it's not helpful for me anymore.

@skarab42
Copy link
Collaborator

Can you mention in which usecases undefined values are a problem for JsonObjects?

Quote from @voxpelli at #272 (comment)

Since this style of JsonObject has been out for so long, I think we should maybe split it up into two types (names for illustration purposes only) to not break things unnecessarily:

  1. JsonObjectStringifiable – one that behaves like today's JsonObject. Would represent a value intended for JSON.stringify() where { foo: undefined } and plain {} are functionally equivalent.
  2. JsonObjectParsed – one that represents an actual parsed JSON value, coming from eg. JSON.parse() where undefined as a value is impossible.

The last fix tried to work around the problem by supporting both solutions at the same time. Obviously this was not the right thing to do and I think the best one is the one proposed by @voxpelli in the quote above.

@peterjuras
Copy link
Author

Thanks for the detail, yes I think these two different types would be great to have!

@skarab42
Copy link
Collaborator

@peterjuras There is a proposal for a Jsonifiable type #492, when it is merged you can use that instead and we will revert to the old version for JsonObject.

  • JSON.stringify(Jsonifiable)
  • JSON.parse() => JsonObject

@peterjuras
Copy link
Author

Thanks, I'll follow the PR! 🙏

@voxpelli
Copy link
Collaborator

@skarab42 I haven't been following a long a lot on this since my comment there, sorry, can you ping me/request a review if you want me to weigh in with some feedback in this area? :) If you stumble across it that is, I'm sure you're also swamped for time

@peterjuras
Copy link
Author

Hi,

I'm trying to use the new Jsonifiable type. One issue I'm running into, is that I can no longer build an interface that extends the Jsonifiable type:

interface CoatLockfileFileEntryBase extends Jsonifiable {
  /**
   * The relative path from the coat project root directory
   */
  path: NonEmptyString;
}

leads to the following error: "An interface can only extend an object type or intersection of object types with statically known members.".

What I would like to achieve is describe an interface/type which does not allow non-jsonifiable types such as functions etc. How can I do that?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

4 participants