Utilizing fp-ts and io-ts for data retrieval and transformation

I'm currently facing a challenge in shaping my fetched data into the desired format, utilizing fp-ts for functional transformation and io-ts for data validation.

My Objective

The goal is for getSchools() to either deliver an Error detailing any issues encountered, or provide an array of validated School objects. While my current code somewhat functions, it fails entirely if just one school in the fetched array does not pass validation. Ideally, I would prefer to filter out the invalid schools and return the valid ones.

Status of My Code

/**
 * API route for all Schools
 */
export default async (_: NextApiRequest, res: NextApiResponse<unknown>) => {
  return new Promise(
    pipe(
      getSchools(),
      fold(
        (e) => of(res.status(400).end(e.message)),
        (v) => of(res.status(200).json(v))
      )
    )
  );
};

/**
 * Handler for fetching Schools
 */
export function getSchools(): TaskEither<Error, Array<School>> {
  return pipe(
    fetch(schoolQuery(schoolQueryBody)),
    chain(mapToSchools),
    chain(decode(t.array(School)))
  );
}

function mapToSchools(
  inputs: Array<any>
): TaskEither<Error, Array<School>> {
  try {
    return right(inputs.map(mapToSchool));
  } catch (e) {
    return left(new Error("Could not map input to school"));
  }
}

export function mapToSchool(input: any): School // Can throw Error

export const schoolQueryBody = `...`;

function fetch(query: string): TaskEither<Error, unknown>

export function decodeError(e: t.Errors): Error {
  const missingKeys = e.map((e) => e.context.map(({ key }) => key).join("."));
  return new Error(`Missing keys: ${missingKeys}`);
}

export const decode = <I, A>(Type: t.Decoder<I, A>) => (
  res: I
): TaskEither<Error, A> => {
  return pipe(fromEither(Type.decode(res)), mapLeft(decodeError));
};

Answer №1

It seems like there are different options available to achieve the desired outcome, as the default behavior of fp-ts / io-ts may not align perfectly with your needs.

The Situation

When parsing a t.array, decoding fails if any value fails to decode. Perhaps you would prefer attempting to decode each value individually rather than using t.array.

Recommendations

A possible approach could be:

import { pipe } from 'fp-ts/lib/function';
import * as ArrayFP from 'fp-ts/lib/Array';

const undecodedSchools: unknown[] = [/* ... school response */];
const schools: School[] = pipe(
  undecodedSchools,
  ArrayFP.map(schoolCodec.decode), // Array<Either<t.Errors, School>>
  ArrayFP.rights, // Takes an Array<Either<E, A>> -> Array<A>
);

This eliminates the need for Either, which may or may not be preferable. If you wish to view errors, you can do something like this instead:

const schools: {
  left: Error[],
  right: School[],
} = pipe(
  undecodedSchools,
  ArrayFP.map(schoolCodec.decode), // Array<Either<t.Errors, School>>
  ArrayFP.separate, // Takes an Array<Either<A, B>> -> Separated<A[], B[]>
);

This separates the different types within the array of eithers.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Looking for assistance in correctly identifying types in react-leaflet for TypeScript?

Embarking on my 'first' project involving react-scripts-ts and react-leaflet. I am attempting to create a class that should be fairly straightforward: import {map, TileLayer, Popup, Marker } from 'react-leaflet'; class LeafletMap exte ...

Tips for incorporating a set offset while utilizing the scrollTop() function

I have successfully implemented a code that sets a position:fixed to a div when it scrolls past the top of the screen. The code I used is as follows: var $window = $(window), $stickyEl = $('#the-sticky-div'), elTop = $stickyEl.o ...

Generate a unique class for each img element randomly

Is there a way to assign each image a unique random class instead of giving all the images the same random class? Any help would be appreciated. $(document.body).ready(function () { bgImageTotal = 5; randomNumber = Math.round(Math.random() * (b ...

Guide on increasing a basic counter and a counter with an object component using redux

(I need help fixing my code) I am trying to write a counter increment code to better understand Redux in the following situations: Increasing a simple counter Increasing a counter with an object Currently facing an issue where counter1 is undefined on the ...

Code malfunctioning

<html> <body> <head> <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js" type="text/javascript"></script> <script type="text/javascript> $('img').click(function(){ var ge ...

Sharing methods between controllers in AngularJS

After coming across this insightful article on Angular validation, I decided to implement it in my project. The validation is functioning perfectly, but I am struggling to access methods in other controllers upon successful form validation. Despite trying ...

Restrict the scope of 'unknown' to an object containing only a string-field without resorting to 'any'

Currently, I am working on validating the data that is being received by my application. To illustrate, consider the following scenario: function extractField(data: unknown): string { if (typeof data !== 'object') { throw new Error(& ...

Methods for obtaining a directive's scope value and utilizing it in another directive within angularjs?

I have a specific requirement to transfer the scope of one directive to another directive and enable two-way binding. The goal is to update JSON values whenever the ng-model changes. In the example below, the JSON contains rowsets with attributes that nee ...

The function signature `(err: any) => void` does not share any properties with the `QueryOptions` type on the Node route

I'm encountering an issue with a route in my Node controller that is causing errors and preventing Node from running properly public async deletePost(req: Request, res: Response) { const { id } = req.params; const deletedPost = await BlogPostM ...

Utilizing Angular controller variables to customize confirm message in HTML

Struggling to include an Angular variable in a confirm message. Despite multiple attempts, I have not been successful with any solution: <a onclick="return confirm('Do you want to manage ' + {{item.name}} + ' with ...?')" ng-hre ...

Testing the API call triggered by the submit button

In my LoginForm template, I have passed the onFinish function to the prop onFinish using Antd Form. Here is the implementation of my onFinish function: This function invokes the api method from class methods Services and AuthService. const onFinish = asyn ...

Implementing debouncing or throttling for a callback function in React using hooks, allowing for continuous updates without having to wait for the user to finish typing

Using React with Hooks and Formik to create a form that includes a preview of the content, I noticed a performance issue when rendering both elements simultaneously. Even though the form works smoothly by itself, it becomes slow and unresponsive when incor ...

Create a fresh JSON object and update the ObjectId in Mongodb through a POST request

I have a web service that connects to mongodb, retrieves an object in JSON format for the user to edit, and then save any changes made back to the database. Now, I am looking to incorporate a "Save as" feature that would allow users to create a new object ...

CSRF validation did not pass. The request has been cancelled. The failure occurred due to either a missing or incorrect CSRF token

Upon hitting the submit button in the login form, I encountered the following error message: Forbidden (403) CSRF verification failed. Request aborted. CSRF token missing or incorrect. settings.py MIDDLEWARE = [ 'django.middleware.security.Secur ...

Unable to retrieve data from file input using jQuery due to undefined property "get(0).files"

I am facing an issue with retrieving file data in jQuery AJAX call from a file input on an ASP.NET view page when clicking a button. HTML <table> <td style="text-align:left"> <input type="file" id="AttachmenteUploadFile" name="Attachme ...

Battle between Comet and Ajax polling

I'm looking to develop a chat similar to Facebook's chat feature. Using Comet would require more memory to maintain the connection. There seems to be a latency issue when using Ajax polling if requests are sent every 3-4 seconds. Considering t ...

Transitioning from a material design list element to a card design

After reviewing the elevation section in the material design docs, I have encountered several challenges when it comes to implementation. I am trying to grasp how to implement this elevation using html+css+js, but my experience in these languages is limit ...

Leveraging custom properties in HTML elements with JavaScript

I am in the process of creating a straightforward battleships game that utilizes a 10x10 table as the playing grid. My goal is to make it easy to adjust the boat length and number of boats, which is why I'm attempting to store data within the HTML obj ...

Set up AngularJS routing for accessing the Web API endpoint

I have encountered an issue with my AngularJS application and Asp.net Web Api. Both applications are hosted on port 80 of the IIS server. The problem arises when the web api URL cannot be accessed due to angularjs routing redirecting API calls to the root ...

Leveraging Angular for REST API Calls with Ajax

app.controller('AjaxController', function ($scope,$http){ $http.get('mc/rest/candidate/pddninc/list',{ params: { callback:'JSON_CALLBACK' } }). success(function (data, status, headers, config){ if(ang ...