-
Notifications
You must be signed in to change notification settings - Fork 327
Description
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.