Skip to content

Filter extra fields in interface decoder #147

@lostintime

Description

@lostintime

Hello @gcanti, thanks for writing this great piece of software!

Here is an issue I discovered while solving similar problems:

Passing extra fields, which are not not defined by t.interface through decode method may turn into unexpected behavior at runtime when it's output assigned to other types with optional fields.

Here is a example:

import * as t from "io-ts"

// We have a response from external service
// Originally it was returning only `username`, `time` field was added later, 
//   which is not breaking compatibility
const serviceResponse: string = JSON.stringify({
  username: "lostintime",
  time: "2018-03-03 14:43:37"
})

// our type definition for service response
const UserProfile = t.interface({
  username: t.string
})

// one more type, using it somewhere deeper, adds a timestamp to user profile
type UserProfileWithTime = {
  username: string
  time?: number // timestamp (seconds)
}

// Parsing response
UserProfile.decode(JSON.parse(serviceResponse))
  .fold(
    e => {
      console.log("Something went wrong", e)
    },
    profile => {
      console.log("User profile is valid:", profile)

      // ... deeper in the call stack
      const prof: UserProfileWithTime = profile
      if (prof.time !== undefined) {
        // UserProfileWithTime.time must be a number
        console.log("1 hour later:", prof.time + 3600) // <<<<< time not a number!
      }
    }
  )

Output:

User profile: { username: 'lostintime', time: '2018-03-03 14:43:37' }
1 hour later: 2018-03-03 14:43:373600

Using strict interfaces is not a desired behavior, for given example, as our client will start to fail when external service deploys new time field in the response.

Filtering extra fields will of course have a performance impact, but to me - current behavior is very dangerous to leave as default.

While decode name implies a change/transformation of the input - changing it this way may not
count as "breaking". validate name is trickier, but it leads to same issue.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions