Extracting values from json request (which could possibly undefined) - javascript

I am trying to learn typescript/js and one of thing i am doing is implement a rest-api using aws-lambda (severless tack). Now, I need to extract body payload to either a construct or just assign those values to variables.
Lets say I am sending following request to signup a user
POST {{userApiEndpoint}}/signup HTTP/1.1
content-type: application/json
{"username":{{username}},"password":{{password}}}
Now I want to extract this username and password from event.body. I have tried multiple things and everytime i am getting errors of string | undefined can't be assigned to string or something similar.
Things that i have tried
export interface User {
username: string | undefined;
password: string | undefined;
}
Option 1: const newUser = event.body? as User; got an error that says Cannot find name 'as'
Option 2: const newUser = event.body as User; got an error which says Conversion of type 'string | undefined' to type 'User' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first.
Option 3: const body = JSON.parse(event.body); got an error which says Argument of type 'string | undefined' is not assignable to parameter of type 'string'.
I am not sure what else to try.

Option 3 should work.
Edit: unless you have explicitly typed your event type as APIGatewayProxyEventV2, in which case the body is of type string | undefined, and the user is:
user = JSON.parse(event.body ?? "") as User
export interface User {
username: string | undefined;
password: string | undefined;
}
// event explicitly typed as APIGatewayProxyEventV2
// the body is typed as string | undefined
const event = {body: '{"username": "foo", "password": "bar"}'} as Record<string, string | undefined>
const user = JSON.parse(event.body ?? "") as User
console.log(user.password)
// Option 3
// the body is typed as any
const event1 = {body: '{"username": "foo", "password": "bar"}'} as Record<string, any>
const user1 = JSON.parse(event1.body) as User
console.log(user1.password)

Related

How to declare in Typescript a dictionary of Generic (in the TS sense) functions

I want to create a series of functions of a Generic type called Handler. Each function gets different but quite specific parameters and returns specific responses, all stated in the Generic definition. A message containing a request with RequestParams comes in and a reply goes back.
A dictionary of such functions is passed to a dispatcher function that will, when called with the handler name and the arguments, to return whatever that function does. To make it simple I declared functions for CRUD operations (plus list) just because everyone knows those.
But my problem is that I have not been able to get rid of the last error, shown in the comment at the end. I tried several ways to do it and been playing whack-a-mole fixing one function and getting another popping up.
I believe I declared too much and made it too restrictive or didn't state the options right. What am I missing? Thanks for your patience.
type ID = string;
type VALUE = string | number | boolean | Date;
type AnyRow = Record<string, VALUE>;
// Default types for Id, In and Out
type DefaultId = ID | undefined;
type DefaultIn = AnyRow | undefined;
// Listings always return an array, even if an empty one, never undefined.
// Reads might return a row or undefined. Tried with `null` as well, to no avail.
type DefaultOut = AnyRow[] | (AnyRow | undefined);
// A handler might receive an Id or not, and it might accept (In) and return (Out) various responses
// - the return is actually a Promise.
// - There is actually a third input, a series of options,
// such as, sort order, field to sort by, page to select
export type Handler<
Id extends DefaultId,
In extends DefaultIn,
Out extends DefaultOut
> = (params: { id: Id; data: In }) => Out;
export type Handlers = Record<
string,
Handler<DefaultId, DefaultIn, DefaultOut>
>;
// Given a series of handlers, `createDispatcher` will return
// a function that when called with the key to a handler and
// an object with the id and data
// returns whatever that keyed function returns, if anything.
export const createDispatcher =
(handlers: Handlers) =>
(fnName: string, requestData: { id: DefaultId; data: DefaultIn }) =>
handlers[fnName](requestData);
// These are the types for a typical CRUD series of handlers, plus list
// They represent the most common combinations of inputs and outputs.
type Resolvers<T extends AnyRow> = {
list: Handler<undefined, undefined, T[] | undefined>;
create: Handler<undefined, T, T>;
read: Handler<ID, undefined, T | undefined>;
update: Handler<ID, T, T>;
delete: Handler<ID, undefined, undefined>;
};
type Data = { a: number };
const handlers: Resolvers<Data> = {
list: () => [{ a: 1 }, { a: 2 }],
read: ({ id }) => (id ? { a: 1 } : { a: 2 }),
create: ({ data }) => data,
update: ({ id, data }) => (id ? data : data),
delete: ({ id }) => (id ? undefined : undefined),
};
// And here I create the actual dispatcher for the Data above
// with the text of the error shown below
export const dataDispatcher = createDispatcher(handlers);
// ^^^^^^^^
// Argument of type 'Resolvers<Data>' is not assignable to parameter of type 'Handlers'.
// Property 'list' is incompatible with index signature.
// Type 'Handler<undefined, undefined, Data[] | undefined>' is not assignable to type 'Handler<DefaultId, DefaultIn, DefaultOut>'.
// Type 'DefaultId' is not assignable to type 'undefined'.
// Type 'string' is not assignable to type 'undefined'.
Playground link
As #kelly pointed out in the comments, the solution is in changing this declaration:
export type Handlers = Record<
string,
Handler<DefaultId, DefaultIn, DefaultOut>
>;
to:
export type Handlers = Record<
string,
Handler<any, any, any>
>;
The issue was unrelated to the rest of the code shown above.
Sorry for the trouble and thanks.

TypeScript(Nodejs error) Type 'string | undefined' is not assignable to type 'string'. Type 'undefined' is not assignable to type 'string

I have a nodejs App and am using Typescript for it and have implemented Classes and interfaces for the Models for Db . I have a model User class with interface too .I simply want to send notification and am using Puhser basic code like this
let pusher = new Pusher({
appId: process.env.PUSHER_APP_ID,
key: process.env.PUSHER_APP_KEY,
secret: process.env.PUSHER_APP_SECRET,
cluster: process.env.PUSHER_APP_CLUSTER
});
pusher.trigger('notifications', 'user_added', user, req.headers['x-socket-id']);
I thought i would be simple but its giving the following error on all the fields like appId,key etc
(property) appId: string | undefined
Type 'string | undefined' is not assignable to type 'string'.
Type 'undefined' is not assignable to type 'string'.ts(2322)
index.d.ts(45, 5): The expected type comes from property 'appId' which is declared here on type 'Options'
i tried using pusher variable as interface but pusher is thirdparty system i tried
let pusher = new Pusher({
const appId: string = process.env.PUSHER_APP_ID,
const key: string = process.env.PUSHER_APP_KEY,
const secret: string = process.env.PUSHER_APP_SECRET,
const cluster: string = process.env.PUSHER_APP_CLUSTER
});
type here
You can cast the environment variables to string.
let pusher = new Pusher({
appId: process.env.PUSHER_APP_ID as string,
key: process.env.PUSHER_APP_KEY as string,
secret: process.env.PUSHER_APP_SECRET as string,
cluster: process.env.PUSHER_APP_CLUSTER as string,
});
becuase process.env.PUSHER_APP_ID value is undefined console.log(process.env.PUSHER_APP_ID) check value
The return type you get from process.env is going to be of type yourRequiredType | undefined because it could exist or not and that is what Typescript is trying to tell you.
You have two options:
You can define the properties of your object to be the desired Type or undefined;
You can implement a Typeguard to guarantee the correct type being passed in like so:
const stringTypeGuard = (x: unknown): x is string => typeof x === string
before you pass your object to the db you can use this to guarantee your properties are what you want
Typescript will not know what variables will be defined in your environment at compile time so it will assume that process.env.{anything} may or may not be defined (i.e. it will be string|undefined if you know for certain that it will be defined then you can say:
let pusher = new Pusher({
appId: process.env.PUSHER_APP_ID!,
key: process.env.PUSHER_APP_KEY!,
secret: process.env.PUSHER_APP_SECRET!,
cluster: process.env.PUSHER_APP_CLUSTER!
});
the ! tells typescript that you know for certain that at the given point the variable will not be null or undefined
In my experience it's better to tupple your way out of it.
Since TypeScript can't be sure that the env var is set, it can be either string or undefined. To remedy this, I always do it like this
let pusher = new Pusher({
appId: process.env.PUSHER_APP_ID || "",
key: process.env.PUSHER_APP_KEY || "",
secret: process.env.PUSHER_APP_SECRET || "",
cluster: process.env.PUSHER_APP_CLUSTER || ""
});
This will look at the env var, and if it's not set, it will simply make it "". This will guarantee that this always will be a string

AWS Lambda error "iam property does not exist" when accessing iam Cognito attribute that I know exists

I am getting an error while trying to access an object that I know exists.
When trying to access the iam property of the event.authorizer object returned from AWS Cognito, an error displays in both VS Code and the terminal once I run it.
src/private.ts:9:33 - error TS2339: Property 'iam' does not exist on type '{ jwt: { claims: { [name: string]: string | number | boolean | string[]; }; scopes: string[]; }; }'.ts(2339)
This error is especially puzzling, because when I print the "event" object, it shows evidence that it exists.
Output of event.requestContext:
{"statusCode":200,"headers":{"Content-type":"text/json"},"body":"{\"event\":{\"accountId\":\"365416766059\",\"apiId\":\"8ihwuguj40\",\"authorizer\":{\"iam\":{\"accessKey\":\"ASIAVKFE... 1068 more characters"}
This shows that there is an object iam that is the value of the authorizer attribute. This is proof that it exists. So why does VS Code have an issue with it?
I ran the code using npx sst start which runs the code with no issues, and returns the expected event object with authorizer and awt nested objects.
Full code for lambda function is below.
import { APIGatewayProxyHandlerV2 } from "aws-lambda"
export const handler: APIGatewayProxyHandlerV2 = async (event) => {
const rand = Math.floor(Math.random() * 10)
const authoriser = event.requestContext.authorizer
const accountId = authoriser?.iam.accountId ?? "" // Problem starts with reference to "iam" here.
return {
statusCode: 200,
headers: {"Content-type": "text/json"},
body: JSON.stringify({ message: `Private random number: ${rand}`, accountId: accountId}),
}
}
I have tried:
Making iam an optional by appending a question mark.
Using a try-catch block around the statement.
event is mis-typed.
By typing your function as APIGatewayProxyHandlerV2, you have told typescript that event is of type APIGatewayProxyEventV2 (typedef here). The actual event from Lambda you are getting is not that type, as evident from the log output.
// what typescript thinks your event is:
export interface APIGatewayProxyEventV2 {
version: string;
routeKey: string;
rawPath: string;
rawQueryString: string;
cookies?: string[] | undefined;
headers: APIGatewayProxyEventHeaders;
queryStringParameters?: APIGatewayProxyEventQueryStringParameters | undefined;
requestContext: {
accountId: string;
apiId: string;
authorizer?: {
jwt: {
claims: { [name: string]: string | number | boolean | string[] };
scopes: string[];
};
} | undefined;
domainName: string;
domainPrefix: string;
...etc
So you need to fix the event type. What are your options?
Search for a pre-defined type that matches the lambda event
Be lazy and self-hating: event: any
Define a minimum type that satisfies the contract: event: MyLambdaEvent
// custom event type
// match the required parts of the event object lambda is actually giving you
interface MyLambdaEvent {
authorizer: {
iam: {
accountId: string
}
}
}

How to declare a type in typescript based on the keys mentioned in the same type?

I want to declare a type in typescript ApiResponse and mention 3 keys in it which are isError, error, and content. What I want is that type should be declared as such that either isError and error exist or content exists.
type ApiResponse<T> = {
isError?: boolean;
error?: ErrorContent;
content: this.isError ? undefined : User; // this is something I want . how should I do it.
}
I want this so that when I call a function which wants a parameter of User type doesn't give an error that the parameter is undefined
We can't define type based on dynamic value here,
one we need to use generic to get User type
Instead of isError boolean we should use status kind of enumeration (success, error)
so that we represent invalid state properly. Try like below,
type ErrorContent = {};
type User = {};
interface SuccessResponse<T> {
status: "success";
content: T; // this is something I want . how should I do it.
}
interface ErrorResponse {
status: "error";
error: ErrorContent;
}
type ApiResponse<T> = SuccessResponse<T> | ErrorResponse;
const success: ApiResponse<User> = {
status: "success",
content: {}
};
const failure: ApiResponse<User> = {
status: "error",
error: {},
};
It is impossible to define type differently by variable. Simply you can define the type using | operator.
type ApiResponse<T> = {
isError?: boolean;
error?: ErrorContent;
content: User | undefined;
}

How to use Generic Types with Lodash Get?

Using lodash's get with generic types to type the return object. However, the type doesn't make sense. In the example below, I cannot supply test to the first param because it is supposed to be null | undefined
The use of get() is that you can get a value of an object without knowing if it exists or not. So I would expect the type to be an explicit any. If I knew that it was null | undefined then I wouldn't try and get a key from that object
example.ts
const test = {
a: false
};
const example = get<boolean>(test, "a", true);
#types/lodash
get(object: null | undefined, path: PropertyPath): undefined;
ts error
Argument of type '{ a: boolean; }' is not assignable to parameter of type 'null | undefined'.
The first generic is not the return value, it's the input value. With your example, it's saying the input is a boolean (or null or undefined).
If you remove it entirely it should work:
const test = {
a: false
};
const example = get(test, "a", true);
Looking at all of the type definitions for get with objects they all have TObject as the first generic: get<TObject extends object, ...>

Categories

Resources