How do you enforce all enum values are keys in an object using zod - zod

I have an enum and I want to ensure that each value in the enum is a key within an object schema.
For example, given the following code, I want the countryToCapital object to contain a mapping for every country in the enum to its respective capital city.
const countriesEnum = z.enum(["USA", "CAN", "MEX"])
const sampleObjectSchema = z.object({
countyToCapital: z.record(countriesEnum, z.string())
});
The record object definitely enforces that the keys can only be values from the enum but it doesn't enforce that all countries must be defined.
sampleObject.countryToCapital.USA // type is 'string | undefined', expected to be 'string'
For some reason the type for the record object is a Partial
sampleObject.countryToCapital // Partial<Record<"USA", "CAN", "MEX">>
How do ensure that all enum values are keys without explicitly adding each one as a key to the object?

I looked through the docs and I'm not seeing a direct way to solve this problem through zod's API. As a work around, you could say:
import { z } from "zod";
const countriesEnum = z.enum(["USA", "CAN", "MEX"])
type CountryToString = {[K in z.infer<typeof countriesEnum>]: string };
// Use a type cast for the correct types
const sampleObjectSchema: z.ZodType<CountryToString> = z.object({
// Safe because we know what is in the enum field
...Object.keys(countriesEnum.enum)
.reduce((obj, key) => ({ ...obj, [key]: z.string() }), {})
}) as z.ZodTypeAny;
console.log(sampleObjectSchema.safeParse({})); // Fails
console.log(sampleObjectSchema.safeParse({
USA: "Washington DC",
CAN: "Ottawa",
MEX: "Mexico City",
})); // Succeeds

Related

How to explicitly skip checking part of the schema in zod

I would like to understand if and how it is possible to skip validating parts of a schema in zod?
In the following example, I would like to validate the foo schema to make sure that the object contains a property id of type number and a property data of type array but (maybe cause there is a lot of data) I would like to prevent validating all the actual array entries in data.
import {z} from 'zod';
const foo = z.object({
id: z.number(),
data: z.array(z.string()),
});
This does the job:
const dataItem = z.custom<DataItem>(); // type DataItem defined by you elsewhere
const foo = z.object({
id: z.number(),
data: z.array(dataItem),
});
// { id: string; data: DataItem[] }
https://github.com/colinhacks/zod/discussions/1575

zod (or toZod): how to model "type" field in discriminated union

I have this type:
export interface ConnectorForModel {
_type: "connector.for.model",
connectorDefinitionId: string
}
I want to model is as a zod schema. Actually I am using toZod, like this:
export const ConnectorForModelZod: toZod<ConnectorForModel> = z.object({
_type: z.literal("connector.for.model"),
connectorDefinitionId: z.string()
});
And I get this type error:
Type 'ZodLiteral<"connector.for.model">' is not assignable to type 'never'.
Whats the right way to express this?
I think the quickest way to get this working is with ZodType:
import { z } from "zod";
export interface ConnectorForModel {
_type: "connector.for.model";
connectorDefinitionId: string;
}
export const ConnectorForModelZod: z.ZodType<ConnectorForModel> = z.object({
_type: z.literal("connector.for.model"),
connectorDefinitionId: z.string()
});
Aside: I tend to define my types from my zod schemas rather than the other way around. If you don't have control over the type that you're working with then the given approach is the way to go, but you could potentially avoid writing the same code twice using z.TypeOf on the zod schema
type ConnectorForModel = z.TypeOf<typeof ConnectorForModelZod>;
This type would be equivalent to your interface.

redux: set state in leaf node of large state tree

I have an Immutable object that holds other Immutable objects, and the redux state tree is set to be an instance of the topmost object:
records.js:
const TypeC = newRecord({
content: null
})
const TypeB = newRecord({
typeC: new TypeC;
})
export const TypeA = newRecord({
typeB: new TypeB;
})
reducer.js:
import {
TypeA,
} from './records';
import types from './types';
const applicationReducer = (state = new TypeA(), action) => {
switch (action.type) {
...
}
default:
return state;
}
};
My question is: how do I write reducer code to change the content field in TypeC? I've tried something like
CASE types.UPDATECONTENT: {
return state.get('typeB').get('typeC').set('content', "new content")
}
but this seems to only return the TypeC segment of my state tree, when I need the entire tree from root.
Pattern for nested objects explained here:
https://redux.js.org/recipes/structuring-reducers/immutable-update-patterns/
state.get('typeB').get('typeC').set('content', "new content") goes down the tree and modifies the attribute "content" of typeC. But since immutable is immutable, it does not modify anything, it returns a new record which has the updated value (!). The things between state and 'typeC' are not touched. Furthermore, set returns the newly created record, so you effectively overwrite state with the updated typeC.
An easy way to update nested objects is using the "deep persistent changes" that are named <operation>In, e.g. setIn. It takes care of creating updated intermediate objects too.
TL/DR:
CASE types.UPDATECONTENT: {
return state.setIn(['typeB', 'typeC','content'], "new content");
}

dynamo db FilterExpression, find in json object using value as key

It is possible to somehow filter results by key name that stored in the same object?
I have JSON object "keys", in property "default" stored key of the object that I need. Is it somehow possible to filter like that keys[keys.default].type = some_type?
var params = {
TableName: 'TABLE_NAME',
IndexName: 'TABLE_INDEX', // optional (if querying an index)
KeyConditionExpression: 'myId = :value',
FilterExpression: '#kmap[#kmap.#def].#tp = :keyval',
ExpressionAttributeNames: {names with special characters
'#kmap': 'keys',
'#tp': 'type',
'#def': 'default'
},
ExpressionAttributeValues: { // a map of substitutions for all attribute values
':value': '1',
':keyval': 'some_type'
},
Limit: 10, // optional (limit the number of items to evaluate)
ProjectionExpression: "displayName, #kmap",
ReturnConsumedCapacity: 'TOTLAL', // optional (NONE | TOTAL | INDEXES)
};
docClient.query(params, function(err, data) {
if (err) ppJson(err); // an error occurred
else ppJson(data); // successful response
});
I'm pretty sure the answer is no.
This keys[keys.default] is not even valid json, as far as I can tell.
Of course, you can do this in two steps:
First, query to get the default key
Then query to get the value
Don't forget, filters are obly applied to the result set - it still requires a libear traversal as specified by your Query or Scan operation.
So you can probably more easily run your query on the client.
And lastly, if this is a typical query ypu need to perform, as an optimization, you can lift the default key and value to be top level attributes on the item. Thrn you can actually create a GSI on that attribure and can actually do efficient lookups.

Turn Undefined Property Access Into an Error

I noticed I can define a type for a function and accidentally access properties on that function
type FakeType = {}
type aFunc = string => number
const b: aFunc = () => 1
const a: FakeType = b.whatTheHeck // Flow makes "whatTheHeck" any type
Is there a way to make this an error in Flow?
I don't really know why Flow doesn't error on this automatically, but what you can do is declare aFunc as a callable object, rather than just a function, e.g.
type aFunc = {
(string): number,
};
(On flowtype.com/try)

Resources