Skip to content

Instantly share code, notes, and snippets.

@dancrumb
Last active January 6, 2023 19:55
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
Star You must be signed in to star a gist
Save dancrumb/82496539cfcb02947220e0b50b8872ec to your computer and use it in GitHub Desktop.
/*
* 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