or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

branded-types-validation.mdcollection-codecs.mddate-handling.mdfunctional-programming-types.mdindex.mdjson-handling.mdstring-number-transformations.mdutility-functions-codec-modifiers.md
tile.json

utility-functions-codec-modifiers.mddocs/

0

# Utility Functions and Codec Modifiers

1

2

Utility functions for creating, modifying, and composing codecs including fallback values, custom validation, and lens generation. These utilities enable advanced codec composition and customization patterns.

3

4

## Capabilities

5

6

### Fallback Values

7

8

Creates codecs that use a fallback value when the original codec validation fails, providing graceful degradation.

9

10

```typescript { .api }

11

/**

12

* Creates a codec that uses a fallback value when validation fails

13

* @param codec - The original codec to try first

14

* @param a - The fallback value to use on validation failure

15

* @param name - Optional name for the codec

16

* @returns Codec that never fails, using fallback on validation errors

17

*/

18

function withFallback<C extends t.Any>(

19

codec: C,

20

a: t.TypeOf<C>,

21

name?: string

22

): C;

23

```

24

25

**Usage Examples:**

26

27

```typescript

28

import { withFallback } from "io-ts-types";

29

import * as t from "io-ts";

30

31

// Number codec with fallback to 0

32

const NumberWithFallback = withFallback(t.number, 0);

33

34

const result1 = NumberWithFallback.decode(42);

35

// Right(42)

36

37

const result2 = NumberWithFallback.decode("invalid");

38

// Right(0) - fallback value used instead of validation error

39

40

const result3 = NumberWithFallback.decode(null);

41

// Right(0) - fallback value used

42

43

// String codec with default value

44

const NameWithDefault = withFallback(t.string, "Anonymous");

45

46

const result4 = NameWithDefault.decode("Alice");

47

// Right("Alice")

48

49

const result5 = NameWithDefault.decode(123);

50

// Right("Anonymous") - fallback used for invalid input

51

```

52

53

### Custom Error Messages

54

55

Creates codecs with custom error messages using a message function, providing more meaningful validation feedback.

56

57

```typescript { .api }

58

/**

59

* Creates a codec with custom error messages

60

* @param codec - The original codec

61

* @param message - Function that generates custom error messages

62

* @returns Codec with custom error reporting

63

*/

64

function withMessage<C extends t.Any>(

65

codec: C,

66

message: (i: t.InputOf<C>, c: t.Context) => string

67

): C;

68

```

69

70

**Usage Examples:**

71

72

```typescript

73

import { withMessage } from "io-ts-types";

74

import * as t from "io-ts";

75

76

// Custom error message for number validation

77

const PositiveNumber = withMessage(

78

t.refinement(t.number, (n) => n > 0, "PositiveNumber"),

79

(input, context) => `Expected a positive number, got: ${JSON.stringify(input)}`

80

);

81

82

const result1 = PositiveNumber.decode(5);

83

// Right(5)

84

85

const result2 = PositiveNumber.decode(-1);

86

// Left([{ message: "Expected a positive number, got: -1", ... }])

87

88

const result3 = PositiveNumber.decode("not-a-number");

89

// Left([{ message: "Expected a positive number, got: \"not-a-number\"", ... }])

90

91

// Custom message for email-like validation

92

const EmailLike = withMessage(

93

t.refinement(t.string, (s) => s.includes("@"), "EmailLike"),

94

(input) => `"${input}" doesn't look like an email address`

95

);

96

97

const result4 = EmailLike.decode("user@example.com");

98

// Right("user@example.com")

99

100

const result5 = EmailLike.decode("invalid-email");

101

// Left([{ message: "\"invalid-email\" doesn't look like an email address", ... }])

102

```

103

104

### Custom Validation

105

106

Creates codecs with custom validate functions while preserving other codec properties.

107

108

```typescript { .api }

109

/**

110

* Creates a codec with a custom validate function

111

* @param codec - The original codec to modify

112

* @param validate - Custom validation function

113

* @param name - Optional name for the codec

114

* @returns Codec with custom validation logic

115

*/

116

function withValidate<C extends t.Any>(

117

codec: C,

118

validate: C['validate'],

119

name?: string

120

): C;

121

```

122

123

**Usage Examples:**

124

125

```typescript

126

import { withValidate } from "io-ts-types";

127

import * as t from "io-ts";

128

import * as E from "fp-ts/lib/Either";

129

130

// Custom validation that normalizes input

131

const TrimmedString = withValidate(

132

t.string,

133

(input, context) => {

134

if (typeof input !== "string") {

135

return t.failure(input, context);

136

}

137

const trimmed = input.trim();

138

return trimmed.length > 0

139

? t.success(trimmed)

140

: t.failure(input, context, "String cannot be empty after trimming");

141

},

142

"TrimmedString"

143

);

144

145

const result1 = TrimmedString.decode(" hello ");

146

// Right("hello") - trimmed

147

148

const result2 = TrimmedString.decode(" ");

149

// Left([ValidationError]) - empty after trimming

150

151

const result3 = TrimmedString.decode(123);

152

// Left([ValidationError]) - not a string

153

```

154

155

### Nullable to Default

156

157

Creates codecs that replace null/undefined inputs with a default value, otherwise uses the original codec.

158

159

```typescript { .api }

160

/**

161

* Creates a codec that replaces null/undefined with a default value

162

* @param codec - The original codec for non-null values

163

* @param a - The default value to use for null/undefined inputs

164

* @param name - Optional name for the codec

165

* @returns Codec that handles nullable inputs with defaults

166

*/

167

function fromNullable<C extends t.Mixed>(

168

codec: C,

169

a: t.TypeOf<C>,

170

name?: string

171

): C;

172

```

173

174

**Usage Examples:**

175

176

```typescript

177

import { fromNullable } from "io-ts-types";

178

import * as t from "io-ts";

179

180

// String with default for null/undefined

181

const StringWithDefault = fromNullable(t.string, "default");

182

183

const result1 = StringWithDefault.decode("hello");

184

// Right("hello")

185

186

const result2 = StringWithDefault.decode(null);

187

// Right("default")

188

189

const result3 = StringWithDefault.decode(undefined);

190

// Right("default")

191

192

const result4 = StringWithDefault.decode(123);

193

// Left([ValidationError]) - number is not string or null/undefined

194

195

// Number array with empty array default

196

const NumberArrayWithDefault = fromNullable(t.array(t.number), []);

197

198

const result5 = NumberArrayWithDefault.decode([1, 2, 3]);

199

// Right([1, 2, 3])

200

201

const result6 = NumberArrayWithDefault.decode(null);

202

// Right([]) - empty array default

203

```

204

205

### Codec from Refinement

206

207

Creates codecs from type guard functions (refinements), enabling custom type validation logic.

208

209

```typescript { .api }

210

/**

211

* Creates a codec from a refinement (type guard) function

212

* @param name - Name for the codec

213

* @param is - Type guard function that determines if input matches type A

214

* @returns Codec that validates using the type guard

215

*/

216

function fromRefinement<A>(

217

name: string,

218

is: (u: unknown) => u is A

219

): t.Type<A, A, unknown>;

220

```

221

222

**Usage Examples:**

223

224

```typescript

225

import { fromRefinement } from "io-ts-types";

226

227

// Custom type guard for positive numbers

228

function isPositiveNumber(u: unknown): u is number {

229

return typeof u === "number" && u > 0;

230

}

231

232

const PositiveNumber = fromRefinement("PositiveNumber", isPositiveNumber);

233

234

const result1 = PositiveNumber.decode(5);

235

// Right(5)

236

237

const result2 = PositiveNumber.decode(-1);

238

// Left([ValidationError])

239

240

const result3 = PositiveNumber.decode("5");

241

// Left([ValidationError]) - string, not number

242

243

// Custom type guard for non-empty arrays

244

function isNonEmptyArray<T>(u: unknown): u is T[] {

245

return Array.isArray(u) && u.length > 0;

246

}

247

248

const NonEmptyArray = fromRefinement("NonEmptyArray", isNonEmptyArray);

249

250

const result4 = NonEmptyArray.decode([1, 2, 3]);

251

// Right([1, 2, 3])

252

253

const result5 = NonEmptyArray.decode([]);

254

// Left([ValidationError])

255

```

256

257

### Lens Generation

258

259

Creates monocle-ts Lens objects for each property of an interface or exact type codec, enabling functional updates.

260

261

```typescript { .api }

262

/**

263

* Interface for exact types that have lenses

264

*/

265

interface ExactHasLenses extends t.ExactType<HasLenses> {}

266

267

/**

268

* Union type for codecs that can have lenses generated

269

*/

270

type HasLenses = t.InterfaceType<any> | ExactHasLenses;

271

272

/**

273

* Creates monocle-ts Lens objects for each property of a codec

274

* @param codec - Interface or exact type codec

275

* @returns Object with lens for each property

276

*/

277

function getLenses<C extends HasLenses>(

278

codec: C

279

): { [K in keyof t.TypeOf<C>]: Lens<t.TypeOf<C>, t.TypeOf<C>[K]> };

280

```

281

282

**Usage Examples:**

283

284

```typescript

285

import { getLenses } from "io-ts-types";

286

import * as t from "io-ts";

287

288

const User = t.type({

289

id: t.number,

290

name: t.string,

291

email: t.string,

292

age: t.number

293

});

294

295

const userLenses = getLenses(User);

296

297

// Now you have lenses for each property

298

const user = { id: 1, name: "Alice", email: "alice@example.com", age: 25 };

299

300

// Update name using lens

301

const updatedUser = userLenses.name.set("Alice Smith")(user);

302

// { id: 1, name: "Alice Smith", email: "alice@example.com", age: 25 }

303

304

// Update age using lens

305

const olderUser = userLenses.age.modify((age) => age + 1)(user);

306

// { id: 1, name: "Alice", email: "alice@example.com", age: 26 }

307

308

// Get property using lens

309

const userName = userLenses.name.get(user);

310

// "Alice"

311

312

// Compose lenses for nested updates (if you had nested objects)

313

const Address = t.type({

314

street: t.string,

315

city: t.string,

316

zipCode: t.string

317

});

318

319

const UserWithAddress = t.type({

320

id: t.number,

321

name: t.string,

322

address: Address

323

});

324

325

const userWithAddressLenses = getLenses(UserWithAddress);

326

const addressLenses = getLenses(Address);

327

328

// Compose lenses for nested property access

329

const streetLens = userWithAddressLenses.address.compose(addressLenses.street);

330

```

331

332

### Output Transformation

333

334

Transforms the output type of a codec by applying a function to the encoded value.

335

336

```typescript { .api }

337

/**

338

* Transforms the output type of a codec

339

* @param codec - The original codec

340

* @param f - Function to transform the output

341

* @param name - Optional name for the codec

342

* @returns Codec with transformed output type

343

*/

344

function mapOutput<A, O, I, P>(

345

codec: t.Type<A, O, I>,

346

f: (p: O) => P,

347

name?: string

348

): t.Type<A, P, I>;

349

```

350

351

**Usage Examples:**

352

353

```typescript

354

import { mapOutput } from "io-ts-types";

355

import * as t from "io-ts";

356

357

// Transform number to string output

358

const NumberAsString = mapOutput(t.number, (n) => n.toString());

359

360

const result1 = NumberAsString.decode(42);

361

// Right(42) - still a number internally

362

363

const encoded1 = NumberAsString.encode(42);

364

// "42" - output transformed to string

365

366

// Transform date to ISO string output

367

const DateAsISO = mapOutput(t.type({ date: t.string }), (obj) => ({

368

...obj,

369

date: new Date(obj.date).toISOString()

370

}));

371

372

const dateObj = { date: "2023-12-25" };

373

const encoded2 = DateAsISO.encode(dateObj);

374

// { date: "2023-12-25T00:00:00.000Z" } - transformed to ISO string

375

```

376

377

### Custom Encoding

378

379

Creates a codec clone with a custom encode function, allowing specialized serialization logic.

380

381

```typescript { .api }

382

/**

383

* Creates a codec clone with a custom encode function

384

* @param codec - The original codec

385

* @param encode - Custom encoding function

386

* @param name - Optional name for the codec

387

* @returns Codec with custom encoding logic

388

*/

389

function withEncode<A, O, I, P>(

390

codec: t.Type<A, O, I>,

391

encode: (a: A) => P,

392

name?: string

393

): t.Type<A, P, I>;

394

```

395

396

**Usage Examples:**

397

398

```typescript

399

import { withEncode } from "io-ts-types";

400

import * as t from "io-ts";

401

402

// Custom encoding for user objects

403

const User = t.type({

404

id: t.number,

405

firstName: t.string,

406

lastName: t.string,

407

email: t.string

408

});

409

410

const UserWithFullName = withEncode(

411

User,

412

(user) => ({

413

...user,

414

fullName: `${user.firstName} ${user.lastName}`

415

})

416

);

417

418

const userData = {

419

id: 1,

420

firstName: "Alice",

421

lastName: "Smith",

422

email: "alice@example.com"

423

};

424

425

const result = UserWithFullName.decode(userData);

426

// Right({ id: 1, firstName: "Alice", lastName: "Smith", email: "alice@example.com" })

427

428

const encoded = UserWithFullName.encode(result.right);

429

// { id: 1, firstName: "Alice", lastName: "Smith", email: "alice@example.com", fullName: "Alice Smith" }

430

```

431

432

### Codec Cloning

433

434

Creates a shallow clone of a codec, preserving the original codec structure while allowing modifications.

435

436

```typescript { .api }

437

/**

438

* Creates a shallow clone of a codec

439

* @param t - The codec to clone

440

* @returns Cloned codec with same prototype and properties

441

*/

442

function clone<C extends t.Any>(t: C): C;

443

```

444

445

**Usage Examples:**

446

447

```typescript

448

import { clone } from "io-ts-types";

449

import * as t from "io-ts";

450

451

const OriginalString = t.string;

452

const ClonedString = clone(OriginalString);

453

454

// Both codecs work identically

455

const result1 = OriginalString.decode("hello");

456

const result2 = ClonedString.decode("hello");

457

// Both: Right("hello")

458

459

// Useful when you need to modify codec properties

460

const ModifiedString = clone(t.string);

461

ModifiedString.name = "CustomString";

462

```

463

464

### Newtype Integration

465

466

Creates codecs from newtypes using newtype-ts iso functions, enabling safe wrapping and unwrapping of carrier types.

467

468

```typescript { .api }

469

/**

470

* Creates a codec from a newtype using iso

471

* @param codec - The codec for the carrier type

472

* @param name - Optional name for the codec

473

* @returns Codec that wraps/unwraps the newtype

474

*/

475

function fromNewtype<N extends AnyNewtype = never>(

476

codec: t.Type<CarrierOf<N>, t.OutputOf<CarrierOf<N>>>,

477

name?: string

478

): t.Type<N, CarrierOf<N>, unknown>;

479

```

480

481

**Usage Examples:**

482

483

```typescript

484

import { fromNewtype } from "io-ts-types";

485

import * as t from "io-ts";

486

import { Newtype, iso } from "newtype-ts";

487

488

// Define a newtype for UserId

489

interface UserId extends Newtype<{ readonly UserId: unique symbol }, number> {}

490

491

// Create iso for the newtype

492

const userIdIso = iso<UserId>();

493

494

// Create codec using fromNewtype

495

const UserIdCodec = fromNewtype<UserId>(t.number);

496

497

const result1 = UserIdCodec.decode(123);

498

// Right(123 as UserId) - wrapped in newtype

499

500

const result2 = UserIdCodec.decode("invalid");

501

// Left([ValidationError]) - not a number

502

503

// Use with newtype operations

504

if (result1._tag === "Right") {

505

const userId = result1.right;

506

const rawNumber = userIdIso.unwrap(userId); // 123

507

const wrappedAgain = userIdIso.wrap(456); // 456 as UserId

508

}

509

```