-
Notifications
You must be signed in to change notification settings - Fork 132
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
Accessing oneof dynamically #129
Comments
We would have to include every oneof member as interface Task_Payload {
payload: {
oneofKind: "renameTask";
renameTask: RenameTask;
noop: undefined;
trimming: undefined;
} | {
oneofKind: "noop";
renameTask: undefined;
noop: NoopTask;
trimming: undefined;
} | {
oneofKind: "trimming";
renameTask: undefined;
noop: undefined;
trimming: TrimmingTask;
} | {
oneofKind: undefined;
renameTask: undefined;
noop: undefined;
trimming: undefined;
};
} That's not very nice to read, which is acceptable, but it also means that you would have to specify each member as payload = {oneofKind: "noop", noop: ..., renameTask: undefined, trimming: undefined};
// gets tedious quickly... I've been passing the entire oneof group around: function workWithPayload(payload: Task_Payload["payload"]) {
// ...
} In my case, it was the right thing to do because it allowed me to easily differentiate the types, which would have been unreliable otherwise . |
I see... However i think it still can be useful to find a way of extracting only the data dynamically. My use case is essentially to grab the data from the const taskInfo = type.payload[type.payload.oneofKind]
Task.create({
name: type.payload.oneofKind,
info: taskInfo,
}); would become: const data: RenameTask | TrimmingTask | NoopTask
switch(taskInfo) {
case 'ranameTask':
data = taskInfo.renameTask;
break;
case 'trimming':
data = taskInfo.trimmingTask;
break
case 'noop':
data = taskInfo.noop;
break
default:
throw new Error("Not supported");
}
Task.create({
name: type.payload.oneofKind,
info: data,
}); I just find that the switch statement is completely redundant in this case, and adds more code to manually maintain. TaskProto.oneOfPayload(taskInfo); // RenameTask | TrimmingTask | NoopTask |
Agreed.
I think it should be possible to merge this oneof union type into a union of the possible values... Ref: |
Are you considering including it in the generated code? |
Yes, but doing this in the runtime is preferable because of less overall code size. |
const getOneOfValue = <
Oneof extends {},
Value extends Oneof extends { oneofKind: keyof Oneof }
? Oneof[Oneof["oneofKind"]]
: never
>(oneof: Oneof): Value => {
const o = (oneof as any);
if (typeof o.oneofKind === 'undefined') throw new Error('No kind specified');
return o[o.oneofKind];
};
declare var exampleOneof: {
oneofKind: "a";
a: null;
} | {
oneofKind: "b";
b: number;
} | {
oneofKind: "c";
c: boolean;
} | {
oneofKind: undefined;
};
const value = getOneOfValue(exampleOneof); // number | boolean | null |
Very nice, @jcready. Are you ok with me adding this to @protobuf-ts/runtime? Or would you like to open a PR? |
@timostamm feel free to use the code. I honestly don't think this function is particularly useful. It doesn't narrow the value and the output doesn't really provide a way to narrow further. I mostly posted it because I don't think it needs to be added to protobuf-ts. In my opinion this is something that @fenos can put in his own code if he finds it useful.
I was trying to mimic @fenos's example code wherein the
If you want a version which doesn't throw for the unset case then this is probably a better version for that: const getOneOfValue = <
Oneof extends {},
Value extends Oneof extends { oneofKind: keyof Oneof }
? Oneof[Oneof["oneofKind"]]
: undefined
>(oneof: Oneof): Value => {
const o = (oneof as any);
return o[o.oneofKind];
};
declare var exampleOneof: {
oneofKind: "a";
a: null;
} | {
oneofKind: "b";
b: number;
} | {
oneofKind: "c";
c: boolean;
} | {
oneofKind: undefined;
};
const value = getOneOfValue(exampleOneof); // number | boolean | null | undefined One issue with either version of the function is that TS doesn't seem to allow me to both: A) Constrain the input type If you try to constrain the Edit: We can add a bit more type safety to the function(s) by adding another level of ternary operators which will output an type ValueFromOneof<Oneof, UnsetValue = undefined> =
Oneof extends { oneofKind: keyof Oneof }
? Oneof[Oneof["oneofKind"]]
: Oneof extends { oneofKind: undefined }
? UnsetValue
: unknown;
const getOneOfValue = <
Oneof extends {},
Value extends ValueFromOneof<Oneof>
>(oneof: Oneof): Value => {
const o = (oneof as any);
return o[o.oneofKind];
};
const getSetOneOfValue = <
Oneof extends {},
Value extends ValueFromOneof<Oneof, never>
>(oneof: Oneof): Value => {
const o = (oneof as any);
if (typeof o.oneofKind === 'undefined') throw new Error('No kind specified');
return o[o.oneofKind];
};
const value = getOneOfValue(exampleOneof); // string | number | boolean | undefined
const setValue = getSetOneOfValue(exampleOneof); // string | number | boolean
const badInput1 = getOneOfValue({oneofKind: 'wat'}); // unknown
const badInput2 = getSetOneOfValue({oneofKind: null}); // unknown
const badInput3 = getOneOfValue({}); // unknown |
Thank you for looking into this so thoroughly, @jcready. We already have They may be helpful in some obscure situations. |
@jcready sorry for the late reply i've been a busy lately. Thanks so much for looking into this! I'm glad they landed in protobuf-ts |
Hi,
I can't find a way nice way to access a
oneof
property type dynamically, maybe you can adviseImagine I have the following one of type:
I want to be able to access the type of whatever object has been received
with the current type definition I'll have to hard code each possible variant where i'd just like to store the value as is.
Any workaround for this still being type safe?
The text was updated successfully, but these errors were encountered: