Giter VIP home page Giter VIP logo

Comments (4)

bhamon avatar bhamon commented on July 4, 2024 1

Thank you for your answer.

There was indeed a lack of discriminator in my code. I've changed it to include a discriminator and a default string encoding for non-DiscriminatedDate.

from typebox.

sinclairzx81 avatar sinclairzx81 commented on July 4, 2024

@bhamon Hi, thanks for reporting!

Yeah, this is definitely a bug. I had carried out some optimization work last weekend to improve Encode/Decode object initialization performance, but it appears I'd introduced a bug which wasn't being caught by the test suite.

Have pushed a fix for this on 0.32.28. If you install this version, your repro should work as expected.

Let me know how you go.
Cheers
S

from typebox.

bhamon avatar bhamon commented on July 4, 2024

Thank you for your time. It works with the new 0.32.28 version.

I also have a question regarding union encoding: does the encoding strategy always use the first element of the union? Or is there a way to enforce it (with an option maybe)?

For example I've defined a date transform as follow:

import {type StringOptions, type NumberOptions, Type, type StaticEncode} from '@sinclair/typebox';

/** Date encoded as an ISO8601-compliant string */
export const DateTimeStringTransform = (_options: StringOptions = {}) =>
  Type.Transform(
    Type.String({
      ..._options,
      format: 'date-time'
    })
  )
    .Decode(v => new Date(v))
    .Encode(v => v.toISOString());

/** Date encoded as a millisecond timestamp number */
export const DateTimeNumberTransform = (_options: NumberOptions = {}) =>
  Type.Transform(Type.Integer(_options))
    .Decode(v => new Date(v))
    .Encode(v => v.getTime());

/** Compound date codec that accepts both string and number representation */
export const DateTimeTransform = (
  _options: {string?: StringOptions; number?: NumberOptions} = {string: {}, number: {}}
) => Type.Union([DateTimeStringTransform(_options.string), DateTimeNumberTransform(_options.number)]);

/* inferred as string|number */
type DateTimeTransformEncode = StaticEncode<ReturnType<typeof DateTimeTransform>>;

export default DateTimeTransform;

The inferred encode type is string | number. It could be syntactically correct. But at run time it always returns a string because a choice has to be made regarding the proper encode to apply.

from typebox.

sinclairzx81 avatar sinclairzx81 commented on July 4, 2024

I also have a question regarding union encoding: does the encoding strategy always use the first element of the union? Or is there a way to enforce it (with an option maybe)?

TypeBox will always return the first matching Union variant, but it's the role of the Encode function to return one of those variants. In the case of Date, to be able to conditionally Encode as either number or string, you will need the Decode function to apply a discriminator to the Date object such that Encode function can make an informed decision on how to encode later.

// Bad Decode (lost information about source type)
string  -> Date
number  -> Date

// Good Decode (union information retained for later Encode)
string -> Date & { type: 'string' }
number -> Date & { type: 'number' }

The following implements a DiscriminatedDate type that achieves the above.

import { Value } from '@sinclair/typebox/value'
import { Type } from '@sinclair/typebox'

// ---------------------------------------------------------
// DiscriminatedDate
// ---------------------------------------------------------
export type DiscriminatedDate = Date & { kind: 'string' | 'number' | ({} & string) }

const DecodeDiscriminatedDate = (value: string | number) => (
  Object.defineProperty(new Date(value), 'kind', { value: typeof value }) 
) as DiscriminatedDate

const EncodeDiscriminatedDate = (value: DiscriminatedDate) => (
  value.kind === 'string' ? value.toISOString() :
  value.kind === 'number' ? value.getTime() :
  value.getTime()
)
const DiscriminatedDate = Type.Transform(Type.Union([Type.Number(),Type.String()]))
  .Decode(DecodeDiscriminatedDate)
  .Encode(EncodeDiscriminatedDate)

// ---------------------------------------------------------
// String
// ---------------------------------------------------------
{
  const D = Value.Decode(DiscriminatedDate, '1970-01-01T00:00:12.345Z')
  const E = Value.Encode(DiscriminatedDate, D)
  console.log('string:')
  console.log('  decode:', D)
  console.log('  encode:', E)
}
// ---------------------------------------------------------
// Number
// ---------------------------------------------------------
{
  const D = Value.Decode(DiscriminatedDate, 12345)
  const E = Value.Encode(DiscriminatedDate, D) 
  console.log('number:')
  console.log('  decode:', D)
  console.log('  encode:', E)
}
// ---------------------------------------------------------
// Embedded
// ---------------------------------------------------------
{
  const User = Type.Transform(Type.Object({ created_at: DiscriminatedDate }))
    .Decode(value => ({ createdAt: value.created_at }))
    .Encode(value => ({ created_at: value.createdAt }))

  const D = Value.Decode(User, { created_at: '1970-01-01T00:00:12.345Z' })
  const E = Value.Encode(User, D)
  console.log('embedded:')
  console.log('  decode:', D)
  console.log('  encode:', E)
}
string:
  decode: 1970-01-01T00:00:12.345Z
  encode: 1970-01-01T00:00:12.345Z
number:
  decode: 1970-01-01T00:00:12.345Z
  encode: 12345
embedded:
  decode: { createdAt: 1970-01-01T00:00:12.345Z }
  encode: { created_at: '1970-01-01T00:00:12.345Z' }

Union transforms are somewhat interesting, but as a general rule, if you are transforming a Union with 10 variants, the Decode function should return a mapping all 10 in such a way the Encode function can later perform the reverse mapping.

Hope this helps, will close off this issue for now
Cheers
S

from typebox.

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.