1.0.0 • Published 5 months ago

@ts-java/optional v1.0.0

Weekly downloads
-
License
-
Repository
github
Last release
5 months ago

@ts-java/optional

A pure Typescript implementation of the Java 8 Optional class.

Features

  • full Typescript support
  • provides methods that handle presence/absence of a value (orElse, ifPresent, etc.)
  • basic implementation of the equals and toString methods

Installation

Install the package using your package manager of choice.

npm

npm install -S @ts-java/optional

yarn

yarn add @ts-java/optional

pnpm

pnpm add @ts-java/optional

Usage

import

import { Optional } from '@ts-java/optional';

// or with require
const { Optional } = require('ts-java/optional');

Creating a new Optional

Optional.empty<T>

This creates an empty Optional, meaning that no value is present for it.

// not providing a generic type will result in Optional<null>.
const optional = Optional.empty();

// you can also provide a generic to satisfy your types
const otherOptional = Optional.empty<string>();

// this will also work if you define a type for the variable
const stringOptional: Optional<string> = Optional.empty();

Optional.of<T>

Use the of method to create a new Optional with the specified value.

Optional.of('foo'); // Optional<string>
Optional.of(10); // Optional<number>
Optional.of(false); // Optional<boolean>
Optional.of({ foo: 'bar' }); // Optional<{foo: string}>
Optional.of(null); // throws a NullPointerException
Optional.of(undefined); // throws a NullPointerException

Optional.ofNullable<T>

If you do not know if your value might be nullable or not, you can use the ofNullable method to avoid a NullPointerException. If the value is null, it will return an empty Optional.

Optional.ofNullable('foo'); // Optional<string | null>
Optional.ofNullable(10); // Optional<number | null>
Optional.ofNullable(false); // Optional<boolean | null>
Optional.ofNullable({ foo: 'bar' }); // Optional<{foo: string} | null>
Optional.ofNullable(null); // Optional<null>;
Optional.ofNullable(undefined); // Optional<undefined | null>

Getting the value of an Optional

get

The preferred alternative to this method is orElseThrow, which was introduced only later in Java 10 (compared to get, which was available since Java 8).

The get method was kept in this package for the sake of completeness.

Returns the value of the Optional. If no value is present (null), it will throw a NoSuchElementException.

Optional.of('foo').get(); // returns "foo";

Optional.ofNullable<string>(null).get(); // throws a NoSuchElementException

orElseThrow()

Returns the value of the Optional. If no value is present (null), it will throw a NoSuchElementException.

Optional.of('foo').orElseThrow(); // returns "foo";

Optional.ofNullable<string>(null).orElseThrow(); // throws a NoSuchElementException

In the Java counterpart, an overload of this method is available where it is possible to provide a custom exceptionSupplier. You can also do this with this package.

Optional.of('foo').orElseThrow(() => new Error('Oops')); // returns "foo";
Optional.ofNullable<string>(null).orElseThrow(() => new Error('Oops')); // throws the Error

Handling the absence of a value

To prevent an exception being thrown by the get methods, you should first check for the presence of the value. It will also exclude null from being a potential value (i.e. when using ofNullable).

const optional = Optional.ofNullable<string>('foo'); // Optional<string | null>
if (optional.isPresent()) {
  optional.orElseThrow().toUpperCase(); // optional will be Optional<string>
}

Alternatively, you can use one of the following methods to take care of empty values.

orElse

Optional.of('foo').orElse('bar'); // returns "foo";
Optional.ofNullable<string>(null).orElse('bar'); // returns "bar"

orElseGet

function getOther(): string {
  return 'bar';
}

Optional.of('foo').orElseGet(getOther); // returns "foo";
Optional.ofNullable<string>(null).orElseGet(getOther); // calls getOther() and returns "bar"

Transforming the Optional

If you already have an Optional, you can use the following methods to transform/filter it

map

Applies the provided mapping function and wraps the transformed value in a new Optional. This does not modify the original value.

const optional = Optional.of(2);

const squared = optional.map((value) => value * value); // returns a new Optional with value=4

flatMap

Similar to map with the difference that it does not wrap the transformed value in a new Optional. This is because the mapping function must already return an Optional.

const optional = Optional.of(2);

const squared = optional.flatMap((value) => Optional.of(value * value)); // returns a new Optional with value=4

filter

Apply a filter on the value of the optional. If the value is present and passes the provided filter check, a new Optional with the value is returned. Otherwise, an empty Optional will be returned.

Optional.of(1).filter((value) => value === 1); // returns a new Optional<number> with value 1
Optional.of(1).filter((value) => value > 1); // returns an empty Optional<number>
Optional.ofNullable<number>(null).filter((value) => value === 1); // returns an empty Optional<number>

Other utility methods

isPresent

This indicates if the Optional has a value. Also acts as typeguard and eliminates null as potential value.

const optional = Optional.ofNullable<string>(null); // Optional<string | null>
if (optional.isPresent()) {
  optional.orElseThrow(); // will not throw an error
}

isEmpty

Indicates that the value of the Optional is absent. Unlike isPresent, this does not act as a typeguard.

const optional = Optional.ofNullable<string>(null); // Optional<string | null>
if (optional.isEmpty()) {
  optional.orElseThrow(); // will definitely throw an error
}

ifPresent

This will call the provided function only if a value is present.

Optional.of('foo').ifPresent((value) => console.log(value)); // logs 'foo'
Optional.ofNullable<string>().ifPresent((value) => console.log(value)); // won't be called

ifPresentOrElse

This will call the provided function with the value, if it is present. If the value is absent, it will call the other function instead.

const action = (value) => console.log(value);
const emptyAction = () => console.log('bar');

Optional.of('foo').ifPresentOrElse(action, emptyAction); // logs 'foo'
Optional.ofNullable<string>().ifPresentOrElse(action, emptyAction); // logs 'bar'

equals

Can be used to check if the value of one Optional is the same of another.

Optional.of(1).equals(Optional.of(1)); // true
Optional.of(1).equals(Optional.of(2)); // false

// also works with objects
Optional.of({ foo: 'bar' }).equals(Optional.of({ foo: 'bar' })); // true

To keep this package as small as possible, it does not rely on any external library for checking equality. A very crude comparison of key-value between objects is used for this implementation - so use with caution when comparing object-values.

toString

Optional.of('foo').toString(); // returns Optional['foo']
Optional.ofNullable(null).toString(); // returns Optional[null]

Links

Contributing

Pull requests are welcome.

  • Fork the repository
  • Create your feature branch: git checkout -b feature/your-feature
  • Make your changes
    • Make sure to follow the code style of the project (Prettier and ESLint are configured)
    • Make sure to add tests for your changes (Vitest is used for testing)
  • Push to the branch
  • Create a new Pull Request

If you have any suggestions, bug reports or questions, feel free to open an issue on GitHub.

Development

To start developing, clone the repository and install the dependencies using pnpm.

pnpm install

Alternatively, you can also clone this repository into a devcontainer which will automatically run pnpm install so you can get started even faster.

Testing

To run the tests, use the test script

pnpm test

Or to run the tests in watch mode

pnpm test:watch

Check the types

To check that the types are correct, use the type-check script

pnpm typecheck

License

This project is licensed under the Unlicense - see the UNLICENSE file for details.

1.0.0

5 months ago