What version of Effect is running?
effect@4.0.0-beta.102
What steps can reproduce the bug?
import { Schema } from "effect"
const schema = Schema.Struct({
createdAt: Schema.DateFromString,
updatedAt: Schema.Date,
name: Schema.String,
})
const standard = Schema.toStandardJSONSchemaV1(Schema.toStandardSchemaV1(schema))
console.log(
JSON.stringify(
standard["~standard"].jsonSchema.input({ target: "draft-2020-12" }),
null,
2
)
)
What is the expected behavior?
createdAt should carry format: "date-time", since its encoded side is an ISO 8601 string:
{
"createdAt": { "type": "string", "format": "date-time" },
"name": { "type": "string" }
}
format: "date-time" is JSON Schema's own vocabulary for this, and the other Standard Schema libraries emit it.
Here is zod@4.4.3 on the equivalent shape, for comparison:
z.toJSONSchema(
z.object({
createdAt: z.iso.datetime(),
updatedAt: z.date(),
name: z.string(),
}),
{ unrepresentable: "any" }
)
{
"createdAt": { "type": "string", "format": "date-time", "pattern": "..." },
"updatedAt": {},
"name": { "type": "string" }
}
arktype emits the keyword too, through its fallback.date option.
What do you see instead?
All three properties render identically, with nothing marking either date:
{
"createdAt": { "type": "string" },
"updatedAt": { "type": "string" },
"name": { "type": "string" }
}
Passing libraryOptions makes no difference.
Note the two distinct problems there. For Schema.DateFromString the description is incomplete: the value really is a string, and the keyword saying which kind of string is missing. For Schema.Date the description is wrong: it is a Date-instance-only schema in v4 (#6481), so a Date is what both sides hold, and "type": "string" describes a value the schema would reject. Zod renders that same case as {}, declining to describe what has no JSON form.
Additional information
This matters for consumers reading a schema through the Standard Schema interface, where the JSON Schema is the entire description available. ~standard.validate returns a decoded value and ~standard.jsonSchema describes the shape, and neither reports that a property is a date. The information exists in the AST (the Declaration node carries annotations.representation.id === "effect/schema/Date"), but a library-agnostic consumer cannot reach it without special-casing Effect.
The practical effect is that a tool accepting any Standard Schema has to ask Effect users to hand-annotate format: "date-time" on a string and pipe it to a date, reimplementing Schema.DateFromString, while Zod and arktype users write their library's normal date spelling and it just works.
Related, both closed and neither covering this: #3662 (JSONSchema.make threw on Schema.Date in v3) and #6481 (v4 migration guide gap for Schema.Date).
Suggested fix
Both paths route through one internal schema, so this looks like a one-line change. Schema.ts:11814:
const DateString = String.annotate({ expected: "a string that will be decoded as a Date" })
DateFromString pipes it (Schema.ts:11921) and Date's toCodecJson links to it (Schema.ts:11856), so annotating it there covers both:
const DateString = String.annotate({
expected: "a string that will be decoded as a Date",
format: "date-time"
})
There is already a precedent for exactly this, in the same file at Schema.ts:13242:
const Base64String = String.annotate({
expected: "a base64 encoded string that will be decoded as Uint8Array",
format: "byte",
contentEncoding: "base64"
})
Schema.Uint8Array consequently emits { "type": "string", "format": "byte", "contentEncoding": "base64" }, which is the shape being asked for here. isUUID does the same thing through a check annotation (Schema.ts:6928).
I confirmed the annotation survives both routes:
const AnnotatedString = Schema.String.annotate({ expected: "x", format: "date-time" })
AnnotatedString
// { "type": "string", "format": "date-time" }
AnnotatedString.pipe(Schema.decodeTo(Schema.Date, SchemaTransformation.dateFromString))
// { "type": "string", "format": "date-time" }
Possibly the same gap nearby
Two neighbouring schemas render as bare strings and have a standard keyword available:
Schema.URL emits { "type": "string" }; JSON Schema has format: "uri".
Schema.DateTimeUtc emits { "type": "string" }; format: "date-time" applies.
Happy to send a PR for any or all of these if the direction is right.
What version of Effect is running?
effect@4.0.0-beta.102
What steps can reproduce the bug?
What is the expected behavior?
createdAtshould carryformat: "date-time", since its encoded side is an ISO 8601 string:{ "createdAt": { "type": "string", "format": "date-time" }, "name": { "type": "string" } }format: "date-time"is JSON Schema's own vocabulary for this, and the other Standard Schema libraries emit it.Here is zod@4.4.3 on the equivalent shape, for comparison:
{ "createdAt": { "type": "string", "format": "date-time", "pattern": "..." }, "updatedAt": {}, "name": { "type": "string" } }arktype emits the keyword too, through its
fallback.dateoption.What do you see instead?
All three properties render identically, with nothing marking either date:
{ "createdAt": { "type": "string" }, "updatedAt": { "type": "string" }, "name": { "type": "string" } }Passing
libraryOptionsmakes no difference.Note the two distinct problems there. For
Schema.DateFromStringthe description is incomplete: the value really is a string, and the keyword saying which kind of string is missing. ForSchema.Datethe description is wrong: it is aDate-instance-only schema in v4 (#6481), so aDateis what both sides hold, and"type": "string"describes a value the schema would reject. Zod renders that same case as{}, declining to describe what has no JSON form.Additional information
This matters for consumers reading a schema through the Standard Schema interface, where the JSON Schema is the entire description available.
~standard.validatereturns a decoded value and~standard.jsonSchemadescribes the shape, and neither reports that a property is a date. The information exists in the AST (theDeclarationnode carriesannotations.representation.id === "effect/schema/Date"), but a library-agnostic consumer cannot reach it without special-casing Effect.The practical effect is that a tool accepting any Standard Schema has to ask Effect users to hand-annotate
format: "date-time"on a string and pipe it to a date, reimplementingSchema.DateFromString, while Zod and arktype users write their library's normal date spelling and it just works.Related, both closed and neither covering this: #3662 (
JSONSchema.makethrew onSchema.Datein v3) and #6481 (v4 migration guide gap forSchema.Date).Suggested fix
Both paths route through one internal schema, so this looks like a one-line change.
Schema.ts:11814:DateFromStringpipes it (Schema.ts:11921) andDate'stoCodecJsonlinks to it (Schema.ts:11856), so annotating it there covers both:There is already a precedent for exactly this, in the same file at
Schema.ts:13242:Schema.Uint8Arrayconsequently emits{ "type": "string", "format": "byte", "contentEncoding": "base64" }, which is the shape being asked for here.isUUIDdoes the same thing through a check annotation (Schema.ts:6928).I confirmed the annotation survives both routes:
Possibly the same gap nearby
Two neighbouring schemas render as bare strings and have a standard keyword available:
Schema.URLemits{ "type": "string" }; JSON Schema hasformat: "uri".Schema.DateTimeUtcemits{ "type": "string" };format: "date-time"applies.Happy to send a PR for any or all of these if the direction is right.