Git Product home page Git Product logo

Comments (10)

mlegenhausen avatar mlegenhausen commented on May 17, 2024 2

@OliverJAsh I think you need to port the Json Type from TypeScript to io-ts. Something like (untested)

export const JsonArray = t.recursion('JsonArray', () => t.readonlyArray(Json))

export const JsonRecord = t.recursion('JsonRecord', () => t.record(t.string, Json))

export const Json = t.union([t.boolean, t.number, t.string, t.null, JsonArray, JsonRecord], 'Json')

Then you can use Json.is

from io-ts.

gobengo avatar gobengo commented on May 17, 2024 1

@gcanti Is this still possible without iots.prism, which has been removed?

from io-ts.

silasdavis avatar silasdavis commented on May 17, 2024 1

Since I stumbled upon it myself and haven't seen it documented prominently io-ts has pipe since 1.0.0 which provides this functionality with something like:

const PersonFromString = JSONFromString.pipe(Person)

Quite handy

from io-ts.

gcanti avatar gcanti commented on May 17, 2024

validate accepts values of type any

export type Validate<T> = (value: any, context: Context) => Validation<T>

so with io-ts you can build types for any input: strings, numbers, objects, ...

Here's a gist

import * as t from 'io-ts'
import { tryCatch } from 'fp-ts/lib/Either'

export type JSONObject = { [key: string]: JSON }
export interface JSONArray extends Array<JSON> {}
export type JSON = null | string | number | boolean | JSONArray | JSONObject

// string -> JSON
const JSONFromString = t.prism(t.string, s => tryCatch<JSON>(() => JSON.parse(s)).toOption(), 'JSONFromString')

// JSON -> Person
const Person = t.interface({
  name: t.string
}, 'Person')

function compose<A, B>(fa: t.Type<A>, fb: t.Type<B>, name: string): t.Type<B> {
  return new t.Type<B>(
    name,
    (v, c) => fa.validate(v, c).chain(a => fb.validate(a, c))
  )
}

// string -> Person
const PersonFromString = compose(JSONFromString, Person, 'PersonFromString')


console.log(t.validate(1, PersonFromString)) // Left([{"value":1,"context":[{"key":"","type":{"name":"PersonFromString"}}]}])
console.log(t.validate('{}', PersonFromString)) // Left([{"context":[{"key":"","type":{"name":"PersonFromString"}},{"key":"name","type":{"name":"string"}}]}])
console.log(t.validate('{"name":"Giulio"}', PersonFromString)) // Right({"name":"Giulio"})

from io-ts.

OliverJAsh avatar OliverJAsh commented on May 17, 2024

Interesting, thanks @gcanti.

I can't decide if I want to have distinct errors for parsing and validation, or whether I want to combine these, like you have done above.

With your first example above, the resulting error context doesn't tell you exactly where the validation failed. It's not clear that validation failed because of a parsing error:

Left([{"value":1,"context":[{"key":"","type":{"name":"PersonFromString"}}]}])

All that really tells me is "I couldn't fit 1 into the type PersonFromString". Using my custom reporter, a parsing error would appear as follows: Expected type to be PersonFromString, but got number: 1.

from io-ts.

OliverJAsh avatar OliverJAsh commented on May 17, 2024

For context, I'm working on twitter-api-ts, and I want parsing to be safe, i.e. return an Either Left instead of throwing an exception.

I'm already using io-ts to validate the API responses match what we expect.

When io-ts validation fails, we return a ValidationErrorsErrorResponse. When parsing fails, we could return the same thing, or we could return a separate ParsingErrorErrorResponse. I'm not sure what makes more sense here.

Related issue: OliverJAsh/twitter-api-ts#2

from io-ts.

gcanti avatar gcanti commented on May 17, 2024

Well, there are many options, you can customize the errors messages managing the context

function compose<A, B>(fa: t.Type<A>, fb: t.Type<B>, name: string): t.Type<B> {
  return new t.Type<B>(
    name,
    (v, c) => fa.validate(v, c.concat({ key: 'parse', type: fa })).chain(a => fb.validate(a, c))
  )
}

import { PathReporter } from 'io-ts/lib/reporters/default'

console.log(PathReporter.report(t.validate('bad', PersonFromString))) // [ 'Invalid value "bad" supplied to : PersonFromString/parse: JSONFromString' ]
console.log(PathReporter.report(t.validate('{}', PersonFromString))) // [ 'Invalid value undefined supplied to : PersonFromString/name: string' ]

or define your own little validation framework

type DecodeResult<A> = Either<string, A>

function json(value: any): DecodeResult<JSON> {
  return t.validate(value, JSONFromString).mapLeft(() => 'Parsing error')
}

function type<A>(type: t.Type<A>): (value: JSON) => DecodeResult<A> {
  return value => {
    const validation = t.validate(value, type)
    return validation.mapLeft(() => PathReporter.report(validation).join('\n'))
  }
}

type Person = t.TypeOf<typeof Person>

function personFromString(value: any): DecodeResult<Person> {
  return json(value).chain(type(Person))
}

console.log(personFromString('bad')) // Left("Parsing error")
console.log(personFromString('{}')) // Left("Invalid value undefined supplied to : Person/name: string")

io-ts is kind of a low-level library you can build upon

from io-ts.

OliverJAsh avatar OliverJAsh commented on May 17, 2024

Looking at JSONFromString, we lose the error object from JSON.parse because we're converting from Either to Option:

const JSONFromString = t.prism(t.string, s => tryCatch<JSON>(() => JSON.parse(s)).toOption(), 'JSONFromString')

It would be good if io-ts allowed you to provide additional information (context?) for an error.

For example, JSON.parse has errors such as:

  • Unexpected token u in JSON at position 0
  • Unexpected end of input

from io-ts.

OliverJAsh avatar OliverJAsh commented on May 17, 2024

For anyone who happens to land here: I made a little helper module that wraps JSON.parse and io-ts validation: https://github.com/OliverJAsh/decode-ts

You have the choice of using the raw error objects returned or using the provided reporter.

from io-ts.

OliverJAsh avatar OliverJAsh commented on May 17, 2024

Here's a new version of JSONFromString that uses the new Either.json in fp-ts.

I'm not sure how to implement the is function though 🤔

export const JSONFromString = new t.Type<E.Json, string, string>(
  'JSONFromString',
  // TODO:
  (x): x is E.Json => true,
  (s, c) =>
    pipe(
      s,
      E.json((_e) => 'whatever'),
      E.fold(() => t.failure(s, c), t.success),
    ),
  JSON.stringify,
);

from io-ts.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.