While Lesiak's response is accurate, it's beneficial to provide further insight. Another method to consider is using as
for casting to a specific type, assuming the cast is dependable; this serves as a reliable alternative to a standard type guard.
For instance, in the following demonstration (playground):
function filterMilestoneCustom(arg: Milestone[]): Array<Milestone & {milestoneLabel: { $case: 'customMilestone' }}> {
return arg.filter(milestone => milestone.milestoneLabel?.$case === 'customMilestone') as any;
}
let milestones: Milestone[] = [];
for (let x of filterMilestoneCustom(milestones)) {
let y: CustomMilestone = x.milestoneLabel.customMilestone;
}
An essential point to note is that there is no need to establish a helper interface. The compiler correctly identifies the type
Milestone & {milestoneLabel: { $case: 'customMilestone' }}
.
The drawback is having to create such a function for each oneof
occurrence. A generic solution could address this issue if ts-proto didn't render the oneof
property optional.
The concept of the oneof
union type in ts-proto originates from my proposal. Subsequently, I developed a protobuf plugin using union types for oneof
, employing undefined
when nothing is selected. In its basic form, the generated interface looks like:
export interface Milestone {
milestoneLabel:
{ $case: 'milestoneType', milestoneType: MilestoneType } |
{ $case: 'customMilestone', customMilestone: CustomMilestone } |
{ $case: undefined };
targetDate: Date | undefined;
}
This enables the creation of a type guard and filtering function as demonstrated below:
type MilestoneCase<C extends Milestone["milestoneLabel"]["$case"]> = Milestone & {milestoneLabel: {$case: C}};
function isMilestoneCase<C extends Milestone["milestoneLabel"]["$case"]>($case: C, milestone: Milestone): milestone is MilestoneCase<C> {
return milestone.milestoneLabel.$case === $case;
}
function filterMilestoneCase<C extends Milestone["milestoneLabel"]["$case"]>(milestones: Milestone[], $case: C): MilestoneCase<C>[] {
const is = (ms: Milestone): ms is MilestoneCase<C> => isMilestoneCase($case, ms);
return milestones.filter(is);
}
This approach can be applied in scenarios like the one depicted in the playground link provided above.
If additional cases are added to the oneof
, there is no requirement to develop a new function separately.
Visit protobuf-ts for insights on the associated plugin functionality. Notably, compared to ts-proto, this plugin is built entirely from scratch without dependencies on protobuf.js or Long.js. Moreover, the code size for web applications is significantly reduced, enabling innovative capabilities with reflection and custom options.