Last active
January 6, 2023 19:55
-
-
Save dancrumb/82496539cfcb02947220e0b50b8872ec to your computer and use it in GitHub Desktop.
TypeScript Implementation of https://docs.oracle.com/javase/8/docs/api/java/util/Optional.html
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* TS Implementation of https://docs.oracle.com/javase/8/docs/api/java/util/Optional.html | |
*/ | |
export class NoSuchElementException extends Error { | |
message: ''; | |
name: string; | |
constructor(message: string = '') { | |
super(message); | |
Object.setPrototypeOf(this, NoSuchElementException.prototype); | |
} | |
} | |
const haveValue = <T>(value: T | undefined): value is T => | |
typeof value !== 'undefined'; | |
/** | |
* An implementation of https://docs.oracle.com/javase/8/docs/api/java/util/Optional.html | |
*/ | |
export class Optional<T> { | |
private readonly value: T | undefined; | |
private __isOptional: 'OPTIONAL'; | |
private constructor(val?: T | Optional<T> | null) { | |
if (val !== null) { | |
if (typeof val === 'object' && '__isOptional' in val) { | |
this.value = val.get(); | |
} else { | |
this.value = val; | |
} | |
} | |
} | |
static empty<T>() { | |
return new Optional<T>(); | |
} | |
static of<T>( | |
value: T | Optional<T> | undefined | null, | |
): Optional<NonNullable<T>> { | |
if (typeof value === 'undefined' || value === null) { | |
return Optional.empty(); | |
} | |
if (typeof value === 'object' && '__isOptional' in value) { | |
const val = value.get(); | |
if (typeof val === 'undefined' || val === null) { | |
return Optional.empty(); | |
} | |
return new Optional<NonNullable<T>>(val as NonNullable<T>); | |
} | |
return new Optional<NonNullable<T>>(value as NonNullable<T>); | |
} | |
filter(predicate: (v: T) => boolean): Optional<T> { | |
if (haveValue(this.value) && predicate(this.value)) { | |
return new Optional(this.value); | |
} | |
return Optional.empty(); | |
} | |
//<U> Optional<U> flatMap(Function<? super T,Optional<U>> mapper) | |
flatMap<U>(mapper: (val: T) => Optional<U>): Optional<U> { | |
if (haveValue(this.value)) { | |
return mapper(this.value); | |
} | |
return Optional.empty<U>(); | |
} | |
get(): T | never { | |
if (haveValue(this.value)) { | |
return this.value; | |
} | |
throw new NoSuchElementException(); | |
} | |
//void ifPresent(Consumer<? super T> consumer) | |
ifPresent(consumer: (val: T) => void): void { | |
if (haveValue(this.value)) { | |
consumer(this.value); | |
} | |
} | |
isPresent(): boolean { | |
return haveValue(this.value); | |
} | |
//<U> Optional<U> map(Function<? super T,? extends U> mapper) | |
map<U>(mapper: (v: T) => U): Optional<U> { | |
if (haveValue(this.value)) { | |
const result = mapper(this.value); | |
if (typeof result !== 'undefined' && result !== null) { | |
return Optional.of(result); | |
} | |
} | |
return Optional.empty<U>(); | |
} | |
orElse(other: T) { | |
if (haveValue(this.value)) { | |
return this.value; | |
} | |
return other; | |
} | |
orNothing(): T | undefined { | |
if (haveValue(this.value)) { | |
return this.value; | |
} | |
return undefined; | |
} | |
orElseGet(other: () => T) { | |
if (haveValue(this.value)) { | |
return this.value; | |
} | |
return other(); | |
} | |
orElseThrow(exceptionSupplier: () => Error): T | never { | |
if (haveValue(this.value)) { | |
return this.value; | |
} | |
throw exceptionSupplier(); | |
} | |
equals(val: Optional<T>) { | |
if (this.isPresent() && val.isPresent()) { | |
return val.get() === this.get(); | |
} | |
return false; | |
} | |
cast<S extends T>(guard: (o: T | S) => boolean): Optional<S> { | |
if (this.isPresent() && guard(this.get())) { | |
return Optional.of(this.get() as S); | |
} | |
return Optional.empty<S>(); | |
} | |
} | |
/* | |
* This can be used to convert an object with Optional<T> types to one | |
* with optional types: | |
* | |
* type Foo = { | |
* bar: Optional<string> | |
* }; | |
* | |
* type OptionalFoo = OptionalToQuestionMark<Foo> | |
* | |
* OptionalFoo === { | |
* bar?: string | |
* } | |
*/ | |
type OptionalPropertyNames<T extends object> = { | |
[K in keyof T]: T[K] extends Optional<infer U> ? K : never; | |
}[keyof T]; | |
type NonOptionalPropertyNames<T extends object> = Exclude< | |
keyof T, | |
OptionalPropertyNames<T> | |
>; | |
export type OptionalToUndefined<T extends object> = { | |
[K in OptionalPropertyNames<T>]?: T[K] extends Optional<infer U> | |
? (U | undefined) | |
: never; | |
} & | |
{ | |
[K in NonOptionalPropertyNames<T>]: T[K]; | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment