Skip to content

Support deserializing Input<T>/Output<T> properties in Config.*Object methods #1123

Description

@eon-pulumi-agent

Summary

Config.GetObject<T>/RequireObject<T>/GetSecretObject<T>/RequireSecretObject<T> deserialize a JSON config value via System.Text.Json.JsonSerializer.Deserialize<T>. This works for plain POCOs, but fails for the pattern commonly used for typed resource arguments, where properties are Input<T>/Output<T> instead of the raw type:

public class GitHubAppArgs : ResourceArgs
{
    public Input<string> AppId { get; set; }
    public Input<string> InstallationAppId { get; set; }
    public Input<string> PrivateKey { get; set; }
}
config:
  github:AppId: "12345"
  github:InstallationAppId: "50000"
  github:PrivateKey:
    secure: <...>

Calling config.RequireObject<GitHubAppArgs>("github") today throws, because System.Text.Json has no converter registered that knows how to construct an Input<string>/Output<string> from a plain JSON string/number/bool.

Originally raised by @jzebedee in #17.

Proposed scope

Add a JsonConverterFactory (and per-type JsonConverter<Input<T>> / JsonConverter<Output<T>>) that:

  • Reads the underlying JSON value for the wrapped type T using the normal JsonSerializer conversion for T.
  • Wraps the resulting value as Input<T>/Output<T> (config values are always known/resolved at this point — there is no "unknown" sentinel needed, unlike the policy-engine's serialization path, since config is never used to represent an in-flight resource output).
  • Is registered by default for the existing *Object config methods (GetObject, RequireObject, GetSecretObject, RequireSecretObject), so no explicit opt-in is required for the common case.

Open questions to resolve during design/implementation

  1. Secrets propagation: RequireSecretObject<T>/GetSecretObject<T> currently wrap the entire deserialized object in a single outer Output.CreateSecret(...). If a nested property is itself an Output<T> produced by the new converter, does it also need to be marked secret, or is the outer wrapping sufficient? (Likely the outer wrapping is sufficient since the whole object is already secret, but this should be explicit in tests/docs.)
  2. JsonSerializerOptions interaction: Allow customization of JsonSerializationOptions #370 / PR Extending Config class to support passing JsonSerializerOptions #373 adds support for passing custom JsonSerializerOptions into the *Object methods. The new Input<T>/Output<T> converter should compose with caller-supplied options (i.e. added as one converter among others, not something that requires replacing the whole options object) rather than conflicting with that work.
  3. Collections and nesting: needs to work for Input<T> inside plain properties, and ideally for common nested cases (e.g. Input<T> inside a List<T> or nested ResourceArgs-shaped class), not just top-level scalar properties.
  4. Unsupported/ambiguous shapes: decide behavior when T itself doesn't support the required JsonSerializer conversion (should surface the existing ConfigTypeException, same as today for other failures).

Why this is separate from #17

This is a genuinely new feature (a custom converter + default wiring), not a bug fix, and changes the shape of what types can be round-tripped through Config deserialization. It deserves its own design discussion and test plan rather than being bundled into the original report, which was mostly about the base case (already working) and the unrelated JsonSerializerOptions ask (tracked in #370).

References

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions