Get local JSON data: The element has a type "any" implicitly - node.js

I have a project in Node JS with Typescript in which I am creating an API to get data from a local JSON file depending on the given variable.
This is my model.ts:
interface ListProductBatchModel {
resp: ResultMsg
}
interface ResultMsg {
name?: string,
price?: string,
error?: string
}
export { ListProductBatchModel, ResultMsg };
This is my properties JSON file:
{
"CT": {
"name": "box",
"price": "5,00"
},
"CC": {
"name": "car",
"price": "6,00"
}
}
This is my controller.ts:
import * as logger from 'winston';
import { Controller, Get, Response, Route, SuccessResponse, Tags } from 'tsoa';
import { ListProductBatchModel } from './models/listProduct.models';
import { ListProductBatchUtils } from './utils/listProductBatch.utils';
#Route('/list/product')
#Tags('list-product')
export class ListProductBatchController {
private listProductBatchUtils: ListProductBatchUtils;
constructor() {
this.listProductBatchUtils = new ListProductBatchUtils();
}
#Get('/{codProduct}')
#SuccessResponse(200, 'Success Response')
async listProductBatch(codProduct: string): Promise<ListProductBatchModel> {
try {
const listProductBatch = await this.listProductBatchUtils.getDataProduct(codProduct);
return Promise.resolve(listProductBatch as ListProductBatchModel);
} catch (error) {
logger.info(JSON.stringify(error));
return Promise.reject(error);
}
}
}
This is my utils.ts:
import * as logger from 'winston';
import * as getProperty from '../json/product.json';
import { ListProductBatchModel, ResultMsg } from '../models/listProduct.models';
export class ListProductBatchUtils {
public async getDataProduct(codProduct: string): Promise<ListProductBatchModel> {
try {
let result: ResultMsg;
if (getProperty[codProduct.toUpperCase()]) {
result = {
name: getProperty[codProduct.toUpperCase()].name,
price: getProperty[codProduct.toUpperCase()].price
}
}else {
result = {
error: "ERROR"
}
}
logger.info('start')
return Promise.resolve({ resp: result });
} catch (error) {
logger.info(JSON.stringify(error));
return Promise.reject(error);
}
}
}
This is the error I get in getProperty [codProduct.toUpperCase ()]:
The element has a type "any" implicitly because the expression of type "string" cannot be used to index the type "{CT: {name: string; price: string;}; CC: {name: string; price : string;};} ".
No index signature was found with a parameter of type "string" in type "{CT: {name: string; price: string;}; CC: {name: string; price: string;};}".
My problem: I don't understand how the error is generated, what I want is to take the name and price properties that match the codProduct variable. Why can this happen? What am I doing wrong and how can I solve it?

Right now, codProduct is a string. When you're accessing getProduct via its subscript [], TypeScript expects you to use an index of getProduct (which, in this case is either "CT" or "CC").
You can satisfy the TypeScript compiler by casting your string as a keyof getProperty's type. Note that this will work at compile time, but will not guarantee that it is in fact a key of getProperty at runtime. But, since you're doing boolean checks already, that seems like it will be okay in your case.
Here's a simplified example:
const getProperty = {
"CT": {
"name": "box",
"price": "5,00"
},
"CC": {
"name": "car",
"price": "6,00"
}
};
type GetPropertyType = typeof getProperty;
function myFunc(input: string) {
const result = getProperty[input.toUpperCase() as keyof GetPropertyType].name;
}

Related

How to return the fields defined by the interface not by the mongoose schema

How can I return "id" insted of "_id" and without "__v" to the client with express, mongoose and TypeScript?
My code below:
Interface
export default interface Domain {
name: string;
objects: DomainObject[]
}
Creation Interface
export default interface DomainCreate {
name: string
}
Mongoose Model
const DomainSchema = new Schema<Domain>({
name: { type: String, required: true },
});
const DomainModel = mongoose.model<Domain>("Domain", DomainSchema);
export { DomainModel, DomainSchema };
Service
export default class DomainService {
public async create(params: DomainCreate): Promise<Domain> {
const domainModel = new DomainModel<Domain>({name: params.name, objects: []});
const domainCreated = await domainModel.save().then();
return domainCreated;
}
}
Controller (POST)
#Post()
#SuccessResponse("201", "Created")
#Response<ValidateErrorJSON>(422, "Validation Failed")
#Response<UnauthorizedErrorJson>(401, "Unauthorized")
#Security("api_key")
public async createDomain(#Body() requestBody: DomainCreate): Promise<Domain> {
const createdDomain = await new DomainService().create(requestBody);
if (createdDomain) this.setStatus(201);
else this.setStatus(500);
return createdDomain;
}
Test:
POST http://localhost:3000/domains
{
"name": "D1"
}
Response:
{
"name": "D1",
"_id": "6291e582ade3b0f8be6921dd",
"__v": 0
}
Expected response:
{
"name": "D1",
"id": "6291e582ade3b0f8be6921dd"
}
There can be different ways to do it, but you can choose normalize-mongoose. It will remove _id, __v and gives you id;
So, in your schema you can add this plugin like this:
import normalize from 'normalize-mongoose';
const DomainSchema = new Schema<Domain>({
name: { type: String, required: true },
});
DomainSchema.plugin(normalize);
const DomainModel = mongoose.model<Domain>("Domain", DomainSchema);
export { DomainModel, DomainSchema };

How can I cast a string to number in an response using axios?

I am using Axios to execute a GET request to a public API. This API return a numeric value as string. I need to cast this value a numeric value. My application runs using tsc, so I expect the result of my object to be a numeric value, but it's not.
Axios Response
[
{
"name": "foo1",
"value": "8123.3000"
},
{
"name": "foo2",
"value": "5132.2003"
},
{
"name": "foo3",
"value": "622.0000"
}
]
Expected Output
[
{
"name": "foo1",
"value": 8123.3
},
{
"name": "foo2",
"value": 5132.2003
},
{
"name": "foo3",
"value": 622
}
]
My code is very simple,
interface MyObj {
myString: string;
myNumber: number;
}
(async () => {
let { data }: AxiosResponse<MyObj> = await axios.get<MyObj>("/public/data");
console.log(data);
})();
I try to use interface, class, the interface Number. Nothing worked.
I leave an example of code to try it.
How can I get the expected output without manually converting each value one by one?
Axios does not change the type of properties in the response. Please verify that the server does not send you the wrong types.
Edit
From your comment, it seems that the server sends you the vslue as string instead of as number. In this case I would suggest working with Ajv (https://github.com/ajv-validator/ajv) so you can create a schema that describes how the response looks like. Ajv cn also transform the value from string to number for you:
const Ajv = require('ajv')
const ajv = new Ajv({
// allow chaning the type of some values from type X to type Y. depends on the source and target type:
// https://ajv.js.org/guide/modifying-data.html#coercing-data-types
// X => Y rules: https://ajv.js.org/coercion.html
coerceTypes: true,
})
const schema = {
type: 'array',
items: {
type: 'object',
properties: {
name: { type: 'string' },
value: { type: 'number' },
},
required: ['name', 'value'],
additionalProperties: false,
},
}
const data = { name: 1, value: '1.1' }
console.log(typeof data.value === 'number') // false!
const valid = ajv.validate(schema, data)
if (!valid) {
console.log(ajv.errors)
process.exit(1)
}
console.log(typeof data.value === 'number') // true!
When declaring that your axios request returns a certain type; this will be checked at compile time and syntax checking. It does not however do this at runtime. If you know that the axios request is returning something different than your interface, you need to convert to that format first. You can do this using the second argument of the JSON.parse function like so:
interface Item {
name: string;
value: number;
}
let responseString = `
[
{
"name": "foo1",
"value": "8123.3000"
},
{
"name": "foo2",
"value": "5132.2003"
},
{
"name": "foo3",
"value": "622.0000"
}
]`
const items: Item[] = JSON.parse(responseString, (key, value) => {
const propertiesToCast = ["value"] // Which properties should be converted from string to number
if (propertiesToCast.includes(key)) {
return parseFloat(value)
}
return value
});
console.log(items)

validating an array of uuids in nestjs swagger body

It sounds like a quite simple question but I've been searching for a solution for a very long time now. I want to validate an array of UUIDs in an endpoint.
Like this:
["9322c384-fd8e-4a13-80cd-1cbd1ef95ba8", "986dcaf4-c1ea-4218-b6b4-e4fd95a3c28e"]
I have already successfully implemented it as a JSON object { "id": ["9322c384-fd8e-4a13-80cd-1cbd1ef95ba8", "986dcaf4-c1ea-4218-b6b4-e4fd95a3c28e"]} with the following code:
public getIds(
#Body(ValidationPipe)
uuids: uuidDto
) {
console.log(uuids);
}
import { ApiProperty } from '#nestjs/swagger';
import { IsUUID } from 'class-validator';
export class uuidDto {
#IsUUID('4', { each: true })
#ApiProperty({
type: [String],
example: [
'9322c384-fd8e-4a13-80cd-1cbd1ef95ba8',
'986dcaf4-c1ea-4218-b6b4-e4fd95a3c28e',
],
})
id!: string;
}
But unfortunately I can't customize the function that calls that endpoint. So I need a solution to only validate a array of uuids.
instead of type string , write string[]. like below:
import { ApiProperty } from '#nestjs/swagger';
import { IsUUID } from 'class-validator';
export class uuidDto {
#IsUUID('4', { each: true })
#ApiProperty({
type: string[],
example: [
'9322c384-fd8e-4a13-80cd-1cbd1ef95ba8',
'986dcaf4-c1ea-4218-b6b4-e4fd95a3c28e',
],
})
id!: string[];
}
You can build a custom validation pipe for it:
#Injectable()
export class CustomClassValidatorArrayPipe implements PipeTransform {
constructor(private classValidatorFunction: (any)) {}
transform(value: any[], metadata: ArgumentMetadata) {
const errors = value.reduce((result, value, index) => {
if (!this.classValidatorFunction(value))
result.push(`${value} at index ${index} failed validation`)
return result
}, [])
if (errors.length > 0) {
throw new BadRequestException({
status: HttpStatus.BAD_REQUEST,
message: 'Validation failed',
errors
});
}
return value;
}
}
In your controller:
#Post()
createExample(#Body(new CustomClassValidatorArrayPipe(isUUID)) body: string[]) {
...
}
Ensure to use the lowercase functions from class-validator. It has to be isUUID instead of IsUUID. (This is used for the manual validation with class-validator.)
CustomClassValidatorArrayPipe is build modular. You can validate any other type with it. For example a MongoId: #Body(new CustomClassValidatorArrayPipe(isMongoId)) body: ObjectId[]
Result
If you send this:
POST http://localhost:3000/example
Content-Type: application/json
[
"986dcaf4-c1ea-4218-b6b4-e4fd95a3c28e",
"123",
"test"
]
Server will reply:
{
"status": 400,
"message": "Validation failed",
"errors": [
"123 at index 1 failed validation",
"test at index 2 failed validation"
]
}

Get all records by category NestJs + MongoDB + Mongoose

I'm using NestJs + MongoDB + Mongoose, and I would like to get all the records in MongoDB with the record that I send by parameter, but I'm not getting it, I'm a beginner. How could I get all records from the same category?
I send the category ID in the request, but I don't receive all the records for that category, could you help me?
I need this:
GET /users/food
and return this:
{
"password": "123",
"name": "Brian",
"adress": "",
"email": "a#a",
"category": "food",
"cpfOrCnpj": "string"
},
{
"password": "123",
"name": "Margo",
"adress": "",
"email": "a#a",
"category": "food",
"cpfOrCnpj": "string"
}
my code:
my service:
import { Injectable } from '#nestjs/common';
import { InjectModel } from '#nestjs/mongoose';
import { User } from './user.model';
import { Model } from 'mongoose';
#Injectable()
export class UserService {
constructor(#InjectModel('User') private readonly userModel: Model<User>) {}
async create(doc: User) {
//Ok
const result = await new this.userModel(doc).save();
return result.id;
}
async find(id: string) {
return await this.userModel.findById(id).exec();
}
async update(user: User) {
//Test
return await this.userModel.findByIdAndUpdate(user);
}
}
my controller:
import { Body, Controller, Get, Param, Post, Put } from '#nestjs/common';
import { UserService } from './user.service';
import { User } from './user.model';
#Controller('user')
export class UserController {
constructor(private service: UserService) {}
#Get(':id')
async find(#Param('category') id: string) {
return this.service.find(id);
}
#Post('create')
create(#Body() user: User) {
return this.service.create(user);
}
#Put('update')
update(#Body() user: User) {
return this.service.update(user);
}
}
In this function
find(id: string) {
return this.userModel.findById(id).exec();
}
you're searching by the _id, findById method is used to filter by the _id of the document
I think category is not the _id of your document here
so, you need to use the normal find method, and pass an object to it
find(id: string) { // id is not _id here, I suggest you to name it category instead
return this.userModel.find({ category: id }).exec();
}
Note, you don't need the async/await here, as you are returning the promise itself
hope it helps

Save data with relation (Nodejs / loopback 4 / mongoDB)

I can not post data json with relation object.
I use mongoDB.
I have 3 table: table_1, table_2, table_3.
I create relation EmbedsMany and EmbedsOne:
- table_2 EmbedsOne table_1.
- table_2 EmbedsMany table_3.
I don't know create post data json to create a new item of table_2 with item of table_1.
import { ..., embedsMany, embedsOne } from '#loopback/repository';
import { Model1, Mode1WithRelations } from './model-1.model';
import { Model3, Model3WithRelations } from './model-2.model';
#model({
settings: {
strictObjectIDCoercion: true,
mongodb: {
collection: 'table_2'
}
}
})
export class Model2 extends Entity {
#property({
type: 'string',
id: true,
mongodb: {
dataType: 'ObjectID' // or perhaps 'objectid'?
}
})
id?: string;
#embedsMany(() => Model3)
model3?: Model3[];
#embedsOne(() => Model1)
model1: Model1;
}
export interface Model2Relations {
// describe navigational properties here
model3?: Model3WithRelations[];
model1: Mode1WithRelations;
}
export type Model2WithRelations = Model2 & Model2Relations;
Repository model 2
import { DefaultCrudRepository } from '#loopback/repository';
import { Model2, Model2Relations } from '../models';
import { DbDataSource } from '../datasources';
import { inject } from '#loopback/core';
export class Model2Repository extends DefaultCrudRepository<
Model2,
typeof Model2.prototype.id,
Model2Relations
> {
constructor(
#inject('datasources.DB') dataSource: DbDataSource,
) {
super(Model2, dataSource);
}
}
Json data post
{
"address": "string",
"status": 1,
"createdAt": "2019-08-04T03:57:12.999Z",
"updatedAt": "2019-08-04T03:57:12.999Z",
"model1": {
"id": "5d465b4cd91e484250d1e54b" /* id of exist item in table_1 */
}
}
Controller is generate by lb4 controller
Expected:
- Item is save success into table_2 with EmbedsOne item of table_1.
Actual:
- Error:
{
"error": {
"statusCode": 422,
"name": "ValidationError",
"message": "The `Model2` instance is not valid. Details: `model1` is not defined in the model (value: undefined).",
"details": {
"context": "Model2",
"codes": {
"project": ["unknown-property"]
},
"messages": {
"model1": ["is not defined in the model"]
}
}
}
}
TL;DR
According to the Loopback 4 team,
#embedsOne
#embedsMany
#referencesOne
#referencesMany
has not implemented yet (2019-Sep-03). (See docs or github)
But I found these decorators and their classes on the sourcecode
So, hopefully, we have to wait untill the implementation is complete. I'll try to update this answer if I got anything new.

Resources