Typegoose: Question about Array<string>

Created on 29 Oct 2020  路  10Comments  路  Source: typegoose/typegoose

I'm struggling to understand a problem with the @prop decorator and an array of strings.

I have the following prop inside of a model:

  @prop({type: String})
    public manufactureLocation?: string[]

I've also tried:

  @prop({type: () => [String]})
    public manufactureLocation?: string[]

And I'm getting the following validation error:

      { CastError: Cast to string failed for value "[ 'AL'  ]" at path "manufactureLocation"
                  at SchemaString.cast (/redacted/carbon-calculator/node_modules/mongoose/lib/schema/string.js:610:11)
                  at SchemaString.SchemaType.applySetters (/redacted/carboncalculator/node_modules/mongoose/lib/schematype.js:1075:12)
                  at model.$set (/redacted/carbon-calculator/node_modules/mongoose/lib/document.js:1250:20)
                  at model._handleIndex (/redacted/carbon-calculator/node_modules/mongoose/lib/document.js:1019:14)
                  at model.$set (/redacted/carbon-calculator/node_modules/mongoose/lib/document.js:960:22)
                  at model.Document (/redacted/carbon-calculator/node_modules/mongoose/lib/document.js:150:12)
                  at model.Model (/redacted/carbon-calculator/node_modules/mongoose/lib/model.js:106:12)
                  at new model (/redacted/carbon-calculator/node_modules/mongoose/lib/model.js:4685:15)
                  at toExecute.push.callback (/redacted/carbon-calculator/node_modules/mongoose/lib/model.js:3051:22)
                  at toExecute.forEach (/redacted/carbon-calculator/node_modules/mongoose/lib/model.js:3087:7)
                  at Array.forEach (<anonymous>)
                  at cb (/redacted/carbon-calculator/node_modules/mongoose/lib/model.js:3086:15)
                  at Promise (/redacted/carbon-calculator/node_modules/mongoose/lib/helpers/promiseOrCallback.js:31:5)
                  at new Promise (<anonymous>)
                  at promiseOrCallback (/redacted/carboncalculator/node_modules/mongoose/lib/helpers/promiseOrCallback.js:30:10)
                  at Function.create (/redacted/carbon-calculator/node_modules/mongoose/lib/model.js:3021:10)
                  at saveECommerceCarbon (webpack-internal:///./utils/models.ts:85:32)
                  at process._tickCallback (internal/process/next_tick.js:68:7)
                stringValue: '"[ \'AL\'  ]"',
                messageFormat: undefined,
                kind: 'string',
                value: [Array],
                path: 'manufactureLocation',
                reason: null } },
                    _message: 'ECommerceCarbon validation failed' }

This is being collected from an online form and the raw JSON value that's being input is: ['AL']. It looks like Mongoose is validating the property as a string rather than an array and thus it's causing problems.

What am I doing wrong? Any help would be appreciated.

Thanks

Versions

  • typegoose: 7.4.1
  • mongoose: 5.10.9
cant reproduce question stale

Most helpful comment

I just figured it out. In my tsconfig I had target set to es5. I saw in your guide that I should have set this to es6, but I ignored it. How stupid of me. Thanks for your help.

Edit: I was also missing emitDecoratorMetadata in my tsconfig

All 10 comments

some questions:

  • are you sure its typegoose 7.4.1?
  • are you sure the input is not an string? (i dont know the error structure well, so i can only guess the input is an string (uneven spaces around 'AL')
  • could you provide sample code from doc creation to save?

Sure:

  1. It's definitiely 7.4.1. I just deleted by lock files and node modules and reinstalled so pretty sure of that
  2. It looks like it could be a string but I just tried setting it directly as an array and got the same error
  3. Sure

Here's the model/class

import * as mongoose from 'mongoose';
import { prop, getModelForClass }  from '@typegoose/typegoose';

export class ECommerceCarbon {

  @prop({type: () => [String]})
  public areaOfRetail?: string[]

  @prop({type: String})
  public manufactureLocation?: string[]

  @prop({type: () => [String]})
  public productMaterials?: string[]

  @prop({type: () => [String]})
  public saleRegion?: string[]

  // @prop()
  // carbon?: number

  // @prop()
  // offsetPrice?: number
}

const ECommerceCarbonModel = getModelForClass(ECommerceCarbon);

export async function saveECommerceCarbon({areaOfRetail, manufactureLocation, productMaterials, saleRegion }: ECommerceCarbon) {
  try {
    console.log("connecting to db");
    await mongoose.connect(process.env.DB_URL, { useNewUrlParser: true, useUnifiedTopology: true, dbName: "carbon-calculator"  });

    console.log("connected to db");
    console.log(typeof areaOfRetail)
    console.log(typeof manufactureLocation)
    console.log(typeof productMaterials)
    console.log(saleRegion)
    await ECommerceCarbonModel.create({
      areaOfRetail: areaOfRetail,
      manufactureLocation: ['AL'],
      productMaterials: productMaterials,
      saleRegion
    });
      console.log("saved model");
      return true;
  } catch (error) {
    console.log("error");
    console.log(error);
    return false
  }

}

Here's where saveEcommerceCarbon gets called:

import { saveECommerceCarbon } from '../../utils/models';
import { NextApiRequest, NextApiResponse } from 'next'


export default async (req: NextApiRequest, res: NextApiResponse) => {
  if (req.method === "POST") {
    console.log(req.body)
    const body = JSON.parse(req.body);
    console.log(body);
    console.log(body.footprint);
    try {
      console.log("saving carbon calc result");

      const r = await saveECommerceCarbon(body.footprint);
      console.log(r);
      res.status(200).json({"result": "success"});
    } catch (e) {
      console.log(`Error: ${e.message}`);
      res.status(500).json({statusCode: 500, message: e.message});
    }
  } else {
    res.setHeader("Allow", "POST");
    res.status(405).end("Method Not Allowed");
  }
};

i tried to reproduce, but it worked how it should

code used:

// NodeJS: 14.13.0
// MongoDB: 4.2-bionic (Docker)
import { getModelForClass, prop } from "@typegoose/typegoose"; // @typegoose/[email protected]
import * as mongoose from "mongoose"; // [email protected] @types/[email protected]

export class ECommerceCarbon {
  @prop({ type: () => [String] })
  public areaOfRetail?: string[];

  @prop({ type: String })
  public manufactureLocation?: string[];

  @prop({ type: () => [String] })
  public productMaterials?: string[];

  @prop({ type: () => [String] })
  public saleRegion?: string[];
}
const ECommerceCarbonModel = getModelForClass(ECommerceCarbon);

(async () => {
  await mongoose.connect(`mongodb://localhost:27017/`, { useNewUrlParser: true, dbName: "verify404", useCreateIndex: true, useUnifiedTopology: true });

  const data = {
    areaOfRetail: ["SomeArea"],
    manufactureLocation: ["SomeLocation"],
    productMaterials: ["SomeMaterial"],
    saleRegion: ["SomeRegion"],
  };

  const doc = await ECommerceCarbonModel.create(data);

  const found = await ECommerceCarbonModel.findById(doc._id);

  console.log(found);

  await mongoose.disconnect();
})();

I'm having a similar problem, so this interests me.

1)

@hmgibson23 @hasezoey
Shouldn't:
@prop({ type: String }) public manufactureLocation?: string[];
be:
@prop({ type: () => [String] }) public manufactureLocation?: string[];

?

2)

@hasezoey To test, I copied your code exactly, and tried to recreate your environment (the only differences are that I'm using node v12.18.3 and I'm using the cloud MongoDB service - but these should not be an issue). I've even copied your mongoose options.

I get the following errors

Uncaught ValidationError: ECommerceCarbon validation failed:
areaOfRetail: Cast to Embedded failed for value "[ 'SomeArea' ]" at path "areaOfRetail",
manufactureLocation: Cast to string failed for value "[ 'SomeLocation' ]" at path "manufactureLocation",
productMaterials: Cast to Embedded failed for value "[ 'SomeMaterial' ]" at path "productMaterials",
saleRegion: Cast to Embedded failed for value "[ 'SomeRegion' ]" at path "saleRegion"

They all follow the same pattern, for example:

areaOfRetail: CastError: Cast to Embedded failed for value "[ 'SomeArea' ]" at path "areaOfRetail"

and for the reason:

ObjectExpectedError: Tried to set nested object field areaOfRetail to array SomeArea and strict mode is set to throw.

How can this happen with the same code? Is it possible something in the environment setup can cause this?

@WeMakeMachines as for 1., it actually dosnt matter, because () => was added for class problems (circular imports) and not for primitives, and as for [Type] was added to not use option dim but the first dimension dosnt matter, to be backwards-compatible

as for 2., something else must be different, what version of mongoose are you using & what compiler/transpiler?

Thanks for your reply. I'm using mongoose 5.10.8, ts-node 9.0.0, typescript 4.0.5 and typegoose 7.4.1 of course. I have experimentalDecorators set to true in my tsconfig. Cloud is using MongoDB 4.2.

@WeMakeMachines do you also have emitDecoratorMetadata enabled, and not use transpile-only?

I just figured it out. In my tsconfig I had target set to es5. I saw in your guide that I should have set this to es6, but I ignored it. How stupid of me. Thanks for your help.

Edit: I was also missing emitDecoratorMetadata in my tsconfig

Marking Issue as stale, will be closed in 7 days if no more activity is seen

closing because issue seems to be solved

Was this page helpful?
0 / 5 - 0 ratings

Related issues

vobence picture vobence  路  8Comments

wxs77577 picture wxs77577  路  5Comments

c0ncentus picture c0ncentus  路  4Comments

remidej picture remidej  路  7Comments

AbderrazzakB picture AbderrazzakB  路  3Comments