Popcorn is a .Net Middleware for your RESTful API that allows your consumers to request exactly as much or as little as they need, with no effort from you.
This project is maintained by Skyward App Company
Zero legacy features are genuine technical non-starters under source generation, except one edge case: truly polymorphic serialization of a type discovered at runtime whose members the trimmer has stripped. Every other feature is portable; what changes is the configuration API surface, not the semantics.
Every legacy feature that uses runtime lambdas for configuration (.Translate(e => e.FirstName + " " + e.LastName), .Authorize<Car>((s,c,v) => ...), .SetInspector((d,c,e) => ...)) is incompatible with source generation as designed, because the generator needs its inputs at build time. Features that get carried forward remap to one of:
Both options are AOT- and trim-safe. The second is more idiomatic modern ASP.NET Core.
Four legacy features were technically feasible under source generation but are explicitly dropped from v2 scope:
Skip/Take and page metadata directly when needed.The complexity of dispatching these through the generator (switch-based type-driven dispatch, DI caching per request, middleware coordination) was not justified by actual usage. Callers who genuinely need these behaviors implement them at the endpoint level with the tools modern ASP.NET already provides.
This decision resolves the “scope decision required before merge” question that previously gated the spike.
GetReferencedTypes BFS walks every reachable type from [JsonSerializable(typeof(ApiResponse<T>))]. Users get the “just declare the type, it works” experience without explicit Map<>() config.? prefix. Unknown include names are silently skipped — no UnknownMappingException equivalent in the generated path.[SubPropertyDefault("[Make,Model]")] attribute read at build time.[PopcornEnvelope] on the type + [PopcornPayload] / [PopcornError] / [PopcornSuccess] on the slot properties. Generator emits typed CreateSuccess / CreateError factories keyed by envelope type.ExpandFrom projections. Technically doable via a generator-emitted ProjectionType.From(Source) copy method, but the v7 MapEntityFramework pattern it would replace was an interception feature (serializer sees S, emits P), not a factory — so [ExpandFrom] wasn’t clean parity. The three real use cases have cleaner answers: [Never] on internal source properties, a hand-written factory, or Mapster.SourceGenerator for complex mapping. See docs/MigrationV7toV8.md §7.System.Text.Json JsonConverter<T> registered on JsonSerializerOptions.Converters covers this cleanly and composes with Popcorn transparently (Popcorn’s generator falls through to JsonSerializer.Serialize for unknown types; STJ picks up the registered converter). See docs/MigrationV7toV8.md §8.UsePopcornExceptionHandler() catches unhandled exceptions, looks up the configured envelope type via PopcornOptions, and writes an ApiError-populated envelope. Envelope shape is source-gen; exception handling is middleware. Clean split.public string FullName => First + " " + Last;) works today, zero framework. 3 passing tests.docs/MigrationV7toV8.md §5.[Factory]-tagged static method.[JsonSerializable], and provide generator diagnostics that fire when a property’s declared type is object/abstract-without-registered-derived-types.Dictionary<string,object>. Legacy SetContext(dict) passed ambient data into lambdas. Under v2, translator methods receive DI services directly as parameters. Same capability, cleaner shape. The dictionary concept is deleted entirely.Tier 1 — MUST ship with v2.0 (core parity):
[SubPropertyDefault] (common include ergonomics)Tier 2 — cleared 2026-04-23. All three planned items ([ExpandFrom], [Translator] with DI, IPopcornBlindHandler<TFrom,TTo>) were dropped after use-case analysis showed each has a cleaner answer using patterns already native to ASP.NET Core + STJ. Documented replacements in docs/MigrationV7toV8.md §5/§7/§8. Polymorphism dispatch via [JsonDerivedType] remains Tier-2-deferred if a consumer asks.
Tier 3 — DEFER to v2.x or drop:
Dictionary<string,object> context (dropped).Dropped from v2 scope:
Type objects, dynamically-built expressions are off-limits.writer.WriteString("Name", source.Name)) — no typeof(T).GetProperty(...).[DynamicallyAccessedMembers] plumbing needed.Span<T>, init, file-scoped namespaces, records, etc. — user models can, but the generator’s ExpanderGenerator.cs cannot.