mojotech / json-type-validation

TypeScript JSON type validation
MIT License
155 stars 15 forks source link

andThen decoder should return intersection type #27

Closed joxerTMD closed 5 years ago

joxerTMD commented 6 years ago

Hi There! Here is my case

class A {
  propA: string

  static decoder(): Decoder<A> {
    return object({
      propA: string()
    })
  }
}

class B extends A {
  propB: string

  static decoder(): Decoder<B> {
    return object({
      propB: string()
    }).andThen(() => A.decoder())
  }
}

This code gives me the following error:

TS2322: Type 'Decoder<A>' is not assignable to type 'Decoder<B>'. 

  Type 'A' is not assignable to type 'B'.     Property 'propB' is missing in type 'A'.

I think that signature of this method should be changed from

andThen: <B>(f: (value: A) => Decoder<B>) => Decoder<B>;

to

andThen: <B>(f: (value: A) => Decoder<B>) => Decoder<A & B>;

I'm going to prepare the PR for this shortly :) Thanks :)

mulias commented 6 years ago

Hey @joxerTMD, thanks so much for providing example code.

Here's how you could write your decoder with andThen:

    static decoder(): Decoder<B> {
      return object({
        propB: string()
      }).andThen(bObj =>
        A.decoder().andThen(aObj =>
          succeed(Object.assign(bObj, aObj))
        )
      );
    }

Let me know if you have any questions about how/why that works.

I don't think it makes sense to make the change you're suggesting, because it restricts the ways andThen can be used. andThen is meant to be a powerful method, but as a side effect it's kind of wordy. I'm ok with that.

What you're probably looking for is an intersection decoder, which has been implemented in this open PR -> https://github.com/mojotech/json-type-validation/pull/22/commits/8bd7476be4648591a7163edd1cd8eff36b76a011. You're welcome to review open PRs if you'd like, I've just been focused on other projects recently.

In the mean time, you could always add a utility decoder for just your use case. On one of my projects I've been using this:

export const extendsDecoder = <A, B>(da: Decoder<A>, db: Decoder<B>): Decoder<A & B> =>
  da.andThen((a: A) => db.andThen((b: B) => succeed(Object.assign({}, a, b))));

static decoder(): Decoder<B> {
  return extendsDecoder(
    A.decoder(),
    object({
      propB: string()
    }));
}

Hopefully that helps!

mulias commented 5 years ago

Solved by #22