data icon indicating copy to clipboard operation
data copied to clipboard

Support for non structured object

Open strozw opened this issue 2 years ago • 13 comments

current my project has model that has non structured object property. it's like below data.

const userConfig1 = {
  id: 1,
  userId: 1,
  config: {}
}

const userConfig2 = {
  id: 1,
  userId: 2,
  config: { meta1: 1, meta2: 2 }
}

How about supporting unstructured object values by the Object type? just like below code.

const db = factory({
  userConfig: {
    id: primaryKey(Number),
    userId: Number
    meta: Object
  }
})

const userConfig1 = db.userConfig.create({ id: 1, userId: 1, meta: {} })
const userConfig2 = db.userConfig.create({ id: 2, userId: 2, meta: { meta1: 1, meta2: 2 } })

strozw avatar Mar 14 '22 16:03 strozw

Hey, @strozw.

This is already supported, I believe. You can use the value getter function to return an empty object if that's the intended value:

factory({
  userConfig: {
    meta: () => ({}),
  }
})

I'd expect that you should be able to create userConfig entities with the following initial values:

db.userConfig.create({}) // "meta: {}"
db.userConfig.create({ meta: {} }) // "meta: {}"
db.userConfig.create({ meta: { meta1: 1 } }) // "meta: { meta1: 1 }"

You'd not have any of the userConfig.meta properties described but that's the expected behavior when you say "unstructured object".

We also support optional properties via optional. This allows you to describe the property but allow for null as initial value. I don't think an empty object would be semantic to allow though, so it's unlikely the solution to use in your case.

kettanaito avatar Mar 15 '22 11:03 kettanaito

@kettanaito

thank you for your quick reply.

I immediately tried the method introduced that is using () => ({}), but the following problems occurred.

First, using () => ({}) as it is caused the following error in typescript. So I had to cast it to any like() => ({} as any).

Diagnostics:
1. Type '() => {}' is not assignable to type 'ModelDefinitionValue'.
     Type '() => {}' is not assignable to type 'ModelValueTypeGetter'.
       Type '{}' is not assignable to type 'ModelValueType'.
         Type '{}' is missing the following properties from type 'PrimitiveValueType[]': length, pop, push, concat, and 28 more.

Next, by using () => ({}), I was able to execute the following model creation code without error, but the actual value of userConfig.meta is{{ It wasn't meta1: 1}, but was the default{}. and I also got the same result for updating the model.

const userConfig = db.userConfig.create({meta: {meta1: 1}})

You can see the above problem in the following playground. https://codesandbox.io/s/modest-fire-bobvlh?file=/src/index.ts

strozw avatar Mar 16 '22 01:03 strozw

This is almost the same error that I get when I try to use getter function to describe nested array field https://github.com/mswjs/data/issues/162

smashercosmo avatar Mar 24 '22 11:03 smashercosmo

I've ended up creating extra dependent object to work around this in the past. Would love to see a proper solution.

tarjei avatar Mar 28 '22 10:03 tarjei

@kettanaito how can we create dynamic object of objects for which the key and value are created based on the mocks.

For example:

export default {
    id: primaryKey(faker.datatype.number),
    name: String
    cars: Object // Not sure what to declared type as right now nothing works
};

//User mocks:
 {
   cars: {
      hyundai: {
         color: red
      }
   }
}

vignesh-kira avatar May 17 '22 15:05 vignesh-kira

@vignesh-kira, there are two ways currently:

Nested object

factory({
  car: {
    id: primaryKey(String),
    manufacturer: {
      name: String
      address: {
        street: String
        houseNumber: String
      }
    }
  }
})

With plain nested objects, the car.manufacturer is a literal object on the car entity and does not exist as a separate entity to be queried.

Use a relationship

factory({
  car: {
    id: primaryKey(String),
    manufacturer: oneOf('company')
  },
  company: {
    id: primaryKey(String),
    name: String,
    address: {
      street: String,
      houseNumber: String
    }
  }
})

With the "car" - "company" relationship, both "car" and "company" (car.manufacturer) are entities that can be queried separately or conjointly.

kettanaito avatar May 18 '22 11:05 kettanaito

Update

I'm currently working on an internal rewrite of how models are parsed in the library. It heavily affects this feature in particular, as I'm unifying how nested model definitions can work.

kettanaito avatar May 18 '22 11:05 kettanaito

Any progress on this @kettanaito ?

tarjei avatar Sep 05 '22 14:09 tarjei

@tarjei, the best way to track the progress on this is through the Releases. When I'm done, it will be a new release.

kettanaito avatar Sep 06 '22 10:09 kettanaito

Hi, just wanted to mention that I am looking forward this 'non-structured' object creation too. I have exactly the same issue as @strozw:

'Next, by using () => ({}), I was able to execute the following model creation code without error, but the actual value of userConfig.meta is{{ It wasn't meta1: 1}, but was the default{}. and I also got the same result for updating the model.'

Hope there is a fix of this soon. Also it would be great if it is possible to add it as nullable(Object)

MarcLopezAvila avatar Oct 15 '22 12:10 MarcLopezAvila

We got bitten by this ourselves just now. We have an unstructured property args that default to an empty {} but could contain anything really. Our first attempt was to just define it as such:

const db = factory({
  model: {
    id: primaryKey(Number),
    name: String,
    args: () => ({}),
  }
});

While this doesn't error, it also doesn't work as args always remain an empty object when creating new instances of model.

hovsater avatar Dec 23 '22 09:12 hovsater

I'd love to use this library to mock data, but this feature is blocking me.

Let me know if this is already possible. I played around with it for a while and couldn't get the desired result.

I have a model with a field that has a nested, recursive structure, like:

type LeafNode = {
  // some fields
}

type Expression = {
  operator: string;
  expressions: (Expression | LeafNode)[ ];
}

const foo = {
  expression: {
    operator: '+',
    expressions: [
      // items are a mix of Expression or LeafNode
    ]
  }
}

But I don't think I can specify that the items in the expressions array are of type Expression or LeafNode.

manzaloros avatar May 04 '23 23:05 manzaloros

I just contributed to the bounty on this issue.

Each contribution to this bounty has an expiry time and will be auto-refunded to the contributor if the issue is not solved before then.

Current bounty reward

To make this a public bounty or have a reward split, the maintainer (@kettanaito) can reply to this comment.

jckw avatar Apr 01 '24 09:04 jckw