prisma icon indicating copy to clipboard operation
prisma copied to clipboard

Define type of content of `Json` field

Open MaximNd opened this issue 4 years ago β€’ 95 comments

Problem

Right now if you have the following schema with Json field:

model User {
  id               Int  @default(autoincrement()) @id
  name             String?
  extendedProfile  Json
}

You'll end up with a problem that you don't have strict type for extendedProfile field in .ts.

const user = prismaService.user.findOne(...);
user.extendedProfile // we don't know the type of extendedProfile

The one way to fix it, is specify some interface in your code and use it like this:

interface UserProfile {
    field1: string;
    field2: number;
}

const user = prismaService.user.findOne(...);
(user.extendedProfile as UserProfile).field1; // now we have autocompletion

But it's not really comfortable to use it like that each time.

Also we can create some class and create instance of it like that:

interface UserProfile {
    field1: string;
    field2: number;
}

class User {
    id: string;
    name?: string;
    extendedProfile: UserProfile;

    constructor(user: PrismaUser /* user object returned by prisma */) {
        // ... initialize
    }
}

const user = new User(prismaService.user.findOne(...));

But this solution creates some overhead due to the creation of an additional object.

Suggested solution

Maybe we can specify type in schema.prisma file like that?

json ExtendedUserProfileJson {
    field1  String
    field2  Int
}

model User {
  id               Int  @default(autoincrement()) @id
  name             String?
  extendedProfile  ExtendedUserProfileJson
}

Alternatives

Alternatively, we can somehow manage this in the typescript.

MaximNd avatar Aug 06 '20 08:08 MaximNd

Have you looked into this?

Advanced usage of generated types

vh13294 avatar Aug 06 '20 12:08 vh13294

Have you looked into this?

Advanced usage of generated types

Yes, but i don't understand how it can help in this problem. I may be wrong, but you cannot define the shape of the Json field using this tutorial..

MaximNd avatar Aug 06 '20 14:08 MaximNd

Interesting idea, though all fields should be nullable since there is no guarantee for JSON format enforced by the database.

Sytten avatar Aug 06 '20 14:08 Sytten

Interesting idea, though all fields should be nullable since there is no guarantee for JSON format enforced by the database.

Probably, if the database you are using does not have JSON support, then you simply cannot use the Json field type and this feature.

MaximNd avatar Aug 06 '20 15:08 MaximNd

Probably, if the database you are using does not have JSON support, then you simply cannot use the Json field type and this feature.

I think @Sytten is talking about the fact in Json type say in postgres doesn't enforce any schema. It will not guarantee that all data there follows the shape you define. The proposal you have defined here is will all be enforced on application level.

Also, @MaximNd why not define a 1-1 relation here if you want a schema to be maintained, just interested to know. The main selling point of Json type personally for me is it allows me to throw data in it without worrying about it's shape.

pantharshit00 avatar Aug 06 '20 15:08 pantharshit00

Probably, if the database you are using does not have JSON support, then you simply cannot use the Json field type and this feature.

I think @Sytten is talking about the fact in Json type say in postgres doesn't enforce any schema. It will not guarantee that all data there follows the shape you define. The proposal you have defined here is will all be enforced on application level.

Also, @MaximNd why not define a 1-1 relation here if you want a schema to be maintained, just interested to know. The main selling point of Json type personally for me is it allows me to throw data in it without worrying about it's shape.

Yes you are right. In this example I can define a 1-1 relationship, or if I store an array in a Json field, I can define a 1-m relationship. But sometimes, when you have a simple data structure (for example, only a few fields) and you know that this json data will be relate only to this entry, then it is easier to define a Json field. The advantage of this is that you don't need to run an additional query or use JOINS to get the relational data. There might also be a rare case when some users that move their database from NoSql like Mongo to relational database. And there will be a lot of destructured data.

MaximNd avatar Aug 06 '20 17:08 MaximNd

Being able to type your Json fields is a simple and understandable feature requests. Although there might be workarounds, this might very well be something that Prisma could offer in the future on its own - so having this feature request is valid.

janpio avatar Aug 06 '20 21:08 janpio

I have tried the following workaround. It works fine, until I need a field other than number or string, e.g. Date. Without date field the approach below works. I tried to use transformers for string to date conversion, but that contradicts Prisma.InputJsonObject definition.

import { Type } from "class-transformer/decorators";
import { IsOptional, Length } from "class-validator";

export class Qualification implements Prisma.InputJsonObject {
  @Length(1, 30)
  name?: string;

  @IsOptional()
  age?:number;

  @IsOptional()
  @Type(() => Date)
  birthday?: Date;

  [index: string]: Prisma.JsonValue ;
}

Any suggestions?

husayt avatar Dec 11 '20 10:12 husayt

@husayt What is this approach? I googled "InputJsonObject" and prisma and only came up with this post and https://github.com/prisma/docs/issues/669 which only has "InputJsonObject" in a list

ahouck avatar Dec 14 '20 14:12 ahouck

A note on potential interaction with #2431 and #2444: As JSON may be used for static or dynamic content, this should be opt in - though ideally, you could still select and filter on specific keys even if you have not typed the JSON field. In the specific use case I'm thinking of, I would actually maintain a separate table that would define which rows contain which keys. Specifically, I have a puzzle with multiple solutions. A solution has a JSON field defining some details which depend on the configuration of the puzzle it is for.

luxaritas avatar Feb 19 '21 02:02 luxaritas

I just ran into a need for this similar to OP's - I am using JSON as a way to store reliably structured data that is small, has several child fields, and is always 1:1 linked with the parent. Because of that, it feels wasteful to break it out into another table and require JOINs on every query.

I was expecting something to be available that was akin to the custom scalar operator in the GraphQL SDL - where you can define a name for a field type (and, in Prisma's case, define what type the column should be given in the schema) and then you are responsible for defining the shape yourself in the client code. You could imagine it working something like:

generator client {
  provider = "prisma-client-js"
  scalarDefinitions {
    RuleContents = "../../../prisma-scalars/RuleContents.ts"
  }
}

scalar RuleContents @underlyingType(Json)

model Rule {
  id  String  @id @default(uuid())
  rejectRule  RuleContents?
  favoriteRule  RuleContents?
}

Then the referenced Typescript file would export an interface with the same name as your scalar. In the simplest form, the client would just be applying a cast for data read from the DB and type checking on data written to or queried from it. As an optional future enhancement the scalar definition file might even be able to export a custom serialize and deserialize function that the Prisma client would use to transform data before writing to/reading from the database.

Finally, if you were generating a client where the scalar type wasn't provided, the client could fall back to the @underlyingType and you'd get the same behavior we have now - e.g. if your underlying type was Json it would fallback to a basic any in Typescript, if your underlying was Unsupported(Polygon) it would fall back to hiding the field, etc.

zackdotcomputer avatar Apr 03 '21 12:04 zackdotcomputer

Adding some light to this, it's a common problem with GraphQL. If an input sets the shape of an object, this will fail type validation when interacting with Prisma. Here is an example where ApplicationFieldConfig[] is coming from my GraphQL library.

image

mmahalwy avatar May 18 '21 21:05 mmahalwy

This would be a huge lifesaver! Right now I have to cast each property to unknown and then my own type, while also removing the need of having our own handling for JSON types.

kladnik avatar Jun 16 '21 11:06 kladnik

I don't want to start a bad trend here, but +1 from me too! I'd like to be able to customize/extend Json column types in TypeScript.

Thanks for all your work here πŸ”Ί!

adrianthedev avatar Jun 18 '21 11:06 adrianthedev

Solved the issue with typechecking in Nest.js application by using class-transformer's classToPlain + plainToClass: image

glebbash avatar Jul 29 '21 11:07 glebbash

I don't want to start a bad trend here, but +1 from me too! I'd like to be able to customize/extend Json column types in TypeScript.

Thanks for all your work here πŸ”Ί!

Yes really mainly because MongoDB is now supported we needed a deep level type-safety, and will also be applicable to Postgres JSON data type

@glebbash solutions works as of now. But at the end you will also wanted a single source of truth, your schema.prisma file

sandbox-apps avatar Aug 15 '21 09:08 sandbox-apps

I'm going to drop a suggestion here. A JSON definition written in Prisma would leave the team to need to maintain parity with Typescript in the schema file. A better solution might be to reference the type alone:

model Post {
  id Int @id @defualt(autoincrement())
  data Json @typescript(PostData)
}

The only difficulty here is I'm not sure how prisma would know where to find the PostData definition.

Another possible solution would be to provide types at the client level:

new PrismaClient<{ Post: { data: PostData } }>()

fny avatar Sep 20 '21 20:09 fny

@fny

new PrismaClient<{ Post: { data: PostData } }>()

Would be a good solution here.

mmahalwy avatar Sep 20 '21 23:09 mmahalwy

@fny @mmahalwy Great ideas :) I love it how it could be a simple fix, that will give an almost instant solution. Even though I think it will make a great solution, I think that the whole point is not creating TS types, and have those synced with the prisma schema right?

nadav-dav avatar Sep 26 '21 07:09 nadav-dav

Yeah, while that would be a pretty easy fix (indeed, one could probably write that as an npm package that wrapped Prisma), it would not actually enforce the type to the same level that Prisma enforces other types from the schema, nor would it necessarily enforce the same type across projects that shared a single schema file for cross-compatibility.

We already accept that Prisma generates Typescript typing for us as part of client generation, so I would rather typing for these json fields were kept in the Prisma schema and generated into TS along with the rest of the models.

zackdotcomputer avatar Sep 28 '21 16:09 zackdotcomputer

We could have a similar approach done by the graphql schema:

type Hotel {
   title: String
   rooms: Room[]
}

type Room {
   name: String
   size: String
}

model Something {
     registration: Int
    hotelData: Json<Hotel>
}

molandim avatar Oct 02 '21 21:10 molandim

Keen

justinsoong avatar Dec 05 '21 23:12 justinsoong

Is there any progress on this ticket? More and more applications are using json columns in their databases and so it would be a great feature to support this, especially because Prisma’s main advantage is type safety.

alex-w0 avatar Dec 25 '21 17:12 alex-w0

Up β€” we really need this feature πŸš€

eakriulin avatar Feb 16 '22 18:02 eakriulin

+1

Is there a place where we can check the priority of this feature? I mean I ❀️ Prisma (I really do!), but it seems quite an obvious feature, no?

binajmen avatar Feb 22 '22 10:02 binajmen

+1 on this this is really needed tbh

emroot avatar Feb 24 '22 19:02 emroot

Is there a place where we can check the priority of this feature? I mean I ❀️ Prisma (I really do!), but it seems quite an obvious feature, no?

https://pris.ly/roadmap @binajmen

pantharshit00 avatar Feb 28 '22 09:02 pantharshit00

Is there a place where we can check the priority of this feature? I mean I ❀️ Prisma (I really do!), but it seems quite an obvious feature, no?

https://pris.ly/roadmap @binajmen

Thank you @pantharshit00. Although typing "json" in the search field does not highlight any backlog entry related to this issue, only 1 archive https://www.notion.so/JSON-field-improvements-5acca22f9a474ab4a8f67e19d412cc25 seems json related, but not answering the feature discussed in this thread.

Is this open issue with 148 πŸ‘ and 30 πŸ‘€ the only way to support this feature implementation?

binajmen avatar Feb 28 '22 10:02 binajmen

Does anybody have a simple workaround for this? We are currently forced to do some TS generic wrapping everywhere we return something from Prisma, as the select fields might change, and if containing the JSON-field, it needs to be converted, like:

 Omit<T, "myJsonField"> & {myJsonField: MyJsonType}

where T is some generic that needs to be filled with typeof stuffReturnedFromPrisma...

which leads to code like:

const stuffReturnedFromPrisma = await prisma.MyModel.create(...);
const typedStuff = stuffReturnedFromPrisma as MyJsonTypeConvert<typeof stuffReturnedFromPrisma>;

or similar. And that's pretty verbose.

nickluger avatar Feb 28 '22 10:02 nickluger

Is this open issue with 148 πŸ‘ and 30 πŸ‘€ the only way to support this feature implementation?

Yes. Or posting additional use cases and information about how you want to use this.

janpio avatar Feb 28 '22 19:02 janpio