-
Notifications
You must be signed in to change notification settings - Fork 8
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Support different naming conventions for object keys #22
Comments
Thanks for your input, @dirkluijk! This wasn't initially considered, but your point is absolutely valid. Let's work on a solution for this. Here's my proposal considering the current limitation of #21:
What do you think? With this solution dont see need to propagate the schema to the adapter Thank you! |
What's the difference between I gave it some more thought. As for different use cases, I think it there are multiple to take into account:
To allow the second case (now or in the future), it would be more practical to work with a callback that matches a source key with a schema key. Hence my suggestion for the const mySchema = z.object({
foo: z.string(),
bar: z.coerce.number(),
});
// assuming this env:
const env = {
FOO: 'Foo!',
BAR: '123',
};
await loadConfig({
schema: mySchema,
matchKey: (sourceKey, schemaKey) => sourceKey.toUpperCase() === schemaKey
adapters: [envAdapter()],
}) However, to me it makes even more sense to not bother the user with these kind of things at all, and make this "relaxed binding" the default behavior of Instead, we’d only provide the user with a simple property to disable this behavior with a There are also cases where certain transformations are clearly adapter-specific. For example, looking at Spring Boot (which is a great reference to me for its flexibility), it allows you to even map environment variables to nested properties. Another feature would be to automatically apply coercion without having to explicitly set this in your schema, since environment variables are always strings. If this is done by adjusting the Zod schema (since it already has coercion built-in) or separately is another implementation detail. These kind of things would of course be very specific to an env adapter implementation. But this does not rule out the global transformation behavior and could be a nice addition to it. |
Thanks for your insights, @dirkluijk! I see how relaxed binding is especially useful when handling configurations from different sources with different naming conventions. The Spring Boot docs you shared are truly inspiring on this topic 😉. Here are the key points to consider:
If I understand correctly, you were referring to cases like the following—am I right? const schema = z.object({
database: z.object({
connection: z.object({
host: z.string(),
port: z.number()
})
})
});
// First source (e.g. default config)
const config1 = {
database: {
connection: {
host: 'localhost',
port: 5432
}
}
};
// Second source (e.g. environment variables)
const config2 = {
'DATABASE_CONNECTION_HOST': 'production.example.com',
'database.connection.port': '6543'
};
// Result after merging and transformation:
{
database: {
connection: {
host: 'production.example.com', // from DATABASE_CONNECTION_HOST
port: 6543 // from database.connection.port
}
}
} |
Yes, exactly! I would consider the feature of relaxed binding (which deals with case transformations only) to be a generic feature, but I would treat the case of matching env variables to nested properties as a separate case specifically for the env adapter, since that is something you only need for environment variables (as they only exist as top-level string values) and might require additional investigation on how to deal with ambiguity. Same applies for coercion, by the way. E.g.
|
Let me know if you accept PRs, I’m willing to contribute. 😁 |
Contributions / PR's are more than welcome for sure @dirkluijk! Feel free to start one. If not, probably I will start one soon :) |
There is currently a limitation (or maybe even a bug) that prevents me from using multiple adapters in
zod-config
, and that is different naming conventions for different sources.A very obvious one is the
envAdapter
: environment variables often use "CONSTANT_CASE", whereas JSON/YAML files often use "PascalCase", "camelCase" or "kebab-case".Considering a schema like:
This looks very similar to the example in the README, but throws an error for me when the adapter reads the keys in upper case:
Looking at the source code, this seems to be intended (as in, not implemented). A simple trick could be to make the matching case insensitive, but this would still not work since some naming conventions introduce dashes or underscores.
I've been thinking about possible solutions, and I have a few ideas:
zod-config
. Leave any transformations up to the user, e.g. by usingz.preprocess(input => ...)
(blocked by Usez.ZodType<object>
instead ofz.AnyZodObject
#21). The downside is that this will lead to a lot of boilerplate in userland code, or forces users to implement their own adapter, which in my opinion defeats the purpose of this library.loadConfig()
, which allows users to match object keys with their corresponding schema keyAdapter
level; so you can configure this per adapterenvAdapter
recognize CONSTANT_CASE and transform the object key to match the schema key.Personally I think it makes most sense to go for solution 3 or 4; as it should up to the adapter to "adapt" the object to match the schema.
For solution 3/4, you could have the option to provide a
KeyMatcher
:Solution 3 would expose this concept to the user, solution 4 would keep this hidden as implementation detail.
Oh, and both solutions would require the adapter to access the schema, see #23.
The text was updated successfully, but these errors were encountered: