Io-ts: Strip unknown properties during encode

Created on 30 Jan 2019  路  8Comments  路  Source: gcanti/io-ts

Given a codec such as the following, I wonder if there would be a generic mechanism by which we could opt into encode behaviour such that undefined properties would be excluded from objects.

import * as t from 'io-ts';

export const MyCodec = t.type({
  name: t.string,
});

const implementsMyCodec = {
  name: 'name',
  propertyNotDefinedInMyCodec: 'value',
};

console.log(MyCodec.encode(implementsMyCodec));
// Output includes the property propertyNotDefinedInMyCodec

When using io-ts as a data-marshalling and validation tool for inputs and outputs, I would like to make sure that I'm not accidentally leaking properties on outputs from my system. It would be great to have io-ts types as the source of truth for this behaviour.

Most helpful comment

@ggoodman I'm working on this right now. My plan is to deprecate the type combinator in favour of a xxx (name to be determined) combinator that strips additional properties while decoding / encoding (and making the exact combinator less useful). In particular I'm working on a PR that modifies the internal intersection algorithm in order to support such a combinator.

All 8 comments

Off-topic: io-ts is a bit of a revelation to me. Keep up the great work!

@ggoodman I'm working on this right now. My plan is to deprecate the type combinator in favour of a xxx (name to be determined) combinator that strips additional properties while decoding / encoding (and making the exact combinator less useful). In particular I'm working on a PR that modifies the internal intersection algorithm in order to support such a combinator.

@gcanti I did also hit the issue with intersection implementation while prototyping own strip combinator: looks like intersection is passing decoded value sequentially to next type, so intersection of 2 stripped types returns wrong result:

const getProps = (type: t.HasProps): t.Props => {
  switch (type._tag) {
    case "RefinementType":
    case "ReadonlyType":
      return getProps(type.type)
    case "InterfaceType":
    case "StrictType":
    case "PartialType":
      return type.props
    case "IntersectionType":
      return type.types.reduce<t.Props>((props, type) => Object.assign(props, getProps(type)), {})
  }
}

export function strip<RT extends t.HasProps>(type: RT): t.Type<t.TypeOf<RT>, t.OutputOf<RT>, t.InputOf<RT>> {
  const props: t.Props = getProps(type)
  const keys = Object.keys(props)
  const len = keys.length

  return new t.Type(
    type.name,
    t.exact(type).is,
    (m, c) => type.validate(m, c).map((o: any) => {
      const r: any = {}

      for (let i = 0; i < len; i++) {
        const k = keys[i]
        if (Object.prototype.hasOwnProperty.call(o, k)) {
          r[k] = o[k]
        }
      }

      return r
    }),
    type.encode
  )
}


  type UserProfile = {
    readonly name: string
    readonly title?: string
  }

const UserProfile: t.Type<UserProfile> = t.intersection([
  strip(t.interface({
    name: t.string
  })),
  strip(t.partial({
    title: t.string
  }))
])



const result = UserProfile.decode({ name: "John", title: "Programmer" })

Resulting value will be an empty object.

@lostintime running your snippet on the intersection branch returns the expected result

console.log(UserProfile.decode({ name: 'John', title: 'Programmer', a: 1 }))
/*
right({
  "name": "John",
  "title": "Programmer"
})
*/
console.log(UserProfile.decode({ name: 'John', a: 1 }))
/*
right({
  "name": "John"
})
*/

deprecate the type combinator in favour of a xxx (name to be determined) combinator that strips additional properties while decoding / encoding (and making the exact combinator less useful)

An even better idea: the very goal of exact is

not accidentally leaking properties on outputs (and input - ed.) from my system

so my plan is

  • do not leak the implementation (never usage) in tuple and exact, i.e. tuple and exact should strip additional components/properties instead of error out while decoding, and strip additional components/properties while encoding
  • un-deprecate strict(props) as an alias for exact(type(props)) instead of adding a new strip combinator

Hi!

I am having some problems to get the strict be.... strict! 馃槃 What I am trying to achieve is declare an interface that does not allow unknown properties. I thought that strict would do it, but it instead silently removes the unknown properties.

I would like that this

{
  includes: [],
  unknownProperty: true,
}

Does _not_ pass the validation of this

t.exact(
  t.interface({
    includes: t.array(t.string),
  })
)

I'm currently using decode for same purpose, but a strict encode would be very useful, as it may not fail

Was this page helpful?
0 / 5 - 0 ratings

Related issues

abaco picture abaco  路  10Comments

rjhilgefort picture rjhilgefort  路  6Comments

philipp-schaerer-lambdait picture philipp-schaerer-lambdait  路  3Comments

leemhenson picture leemhenson  路  6Comments

steida picture steida  路  3Comments