@@ -542,12 +542,14 @@ export class JSONSchemaGenerator {
542
542
// metadata
543
543
const meta = this . metadataRegistry . get ( schema ) ;
544
544
if ( meta ) Object . assign ( result . schema , meta ) ;
545
- if ( this . io === "input" && def . type === "pipe" ) {
545
+
546
+ if ( this . io === "input" && isTransforming ( schema ) ) {
546
547
// examples/defaults only apply to output type of pipe
547
548
delete result . schema . examples ;
548
549
delete result . schema . default ;
549
- if ( result . schema . _prefault ) result . schema . default = result . schema . _prefault ;
550
550
}
551
+
552
+ // set prefault as default
551
553
if ( this . io === "input" && result . schema . _prefault ) result . schema . default ??= result . schema . _prefault ;
552
554
delete result . schema . _prefault ;
553
555
@@ -604,6 +606,8 @@ export class JSONSchemaGenerator {
604
606
return { defId, ref : defUriPrefix + defId } ;
605
607
} ;
606
608
609
+ // stored cached version in `def` property
610
+ // remove all properties, set $ref
607
611
const extractToDef = ( entry : [ schemas . $ZodType < unknown , unknown > , Seen ] ) : void => {
608
612
if ( entry [ 1 ] . schema . $ref ) {
609
613
return ;
@@ -680,27 +684,31 @@ export class JSONSchemaGenerator {
680
684
const seen = this . seen . get ( zodSchema ) ! ;
681
685
const schema = seen . def ?? seen . schema ;
682
686
683
- const _schema = { ...schema } ;
687
+ const _cached = { ...schema } ;
688
+
689
+ // already seen
684
690
if ( seen . ref === null ) {
685
691
return ;
686
692
}
687
693
694
+ // flatten ref if defined
688
695
const ref = seen . ref ;
689
- seen . ref = null ;
696
+ seen . ref = null ; // prevent recursion
690
697
if ( ref ) {
691
698
flattenRef ( ref , params ) ;
692
699
700
+ // merge referenced schema into current
693
701
const refSchema = this . seen . get ( ref ) ! . schema ;
694
-
695
702
if ( refSchema . $ref && params . target === "draft-7" ) {
696
703
schema . allOf = schema . allOf ?? [ ] ;
697
704
schema . allOf . push ( refSchema ) ;
698
705
} else {
699
706
Object . assign ( schema , refSchema ) ;
700
- Object . assign ( schema , _schema ) ; // this is to prevent overwriting any fields in the original schema
707
+ Object . assign ( schema , _cached ) ; // prevent overwriting any fields in the original schema
701
708
}
702
709
}
703
710
711
+ // execute overrides
704
712
if ( ! seen . isParent )
705
713
this . override ( {
706
714
zodSchema : zodSchema as schemas . $ZodTypes ,
@@ -723,6 +731,7 @@ export class JSONSchemaGenerator {
723
731
724
732
Object . assign ( result , root . def ) ;
725
733
734
+ // build defs object
726
735
const defs : JSONSchema . BaseSchema [ "$defs" ] = params . external ?. defs ?? { } ;
727
736
for ( const entry of this . seen . entries ( ) ) {
728
737
const seen = entry [ 1 ] ;
@@ -802,3 +811,107 @@ export function toJSONSchema(
802
811
803
812
return gen . emit ( input , _params ) ;
804
813
}
814
+
815
+ function isTransforming (
816
+ _schema : schemas . $ZodType ,
817
+ _ctx ?: {
818
+ seen : Set < schemas . $ZodType > ;
819
+ }
820
+ ) : boolean {
821
+ const ctx = _ctx ?? { seen : new Set ( ) } ;
822
+
823
+ if ( ctx . seen . has ( _schema ) ) return false ;
824
+ ctx . seen . add ( _schema ) ;
825
+
826
+ const schema = _schema as schemas . $ZodTypes ;
827
+ const def = schema . _zod . def ;
828
+ switch ( def . type ) {
829
+ case "string" :
830
+ case "number" :
831
+ case "bigint" :
832
+ case "boolean" :
833
+ case "date" :
834
+ case "symbol" :
835
+ case "undefined" :
836
+ case "null" :
837
+ case "any" :
838
+ case "unknown" :
839
+ case "never" :
840
+ case "void" :
841
+ case "literal" :
842
+ case "enum" :
843
+ case "nan" :
844
+ case "file" :
845
+ case "template_literal" :
846
+ return false ;
847
+ case "array" : {
848
+ return isTransforming ( def . element , ctx ) ;
849
+ }
850
+ case "object" : {
851
+ for ( const key in def . shape ) {
852
+ if ( isTransforming ( def . shape [ key ] , ctx ) ) return true ;
853
+ }
854
+ return false ;
855
+ }
856
+ case "union" : {
857
+ for ( const option of def . options ) {
858
+ if ( isTransforming ( option , ctx ) ) return true ;
859
+ }
860
+ return false ;
861
+ }
862
+ case "intersection" : {
863
+ return isTransforming ( def . left , ctx ) || isTransforming ( def . right , ctx ) ;
864
+ }
865
+ case "tuple" : {
866
+ for ( const item of def . items ) {
867
+ if ( isTransforming ( item , ctx ) ) return true ;
868
+ }
869
+ if ( def . rest && isTransforming ( def . rest , ctx ) ) return true ;
870
+ return false ;
871
+ }
872
+ case "record" : {
873
+ return isTransforming ( def . keyType , ctx ) || isTransforming ( def . valueType , ctx ) ;
874
+ }
875
+ case "map" : {
876
+ return isTransforming ( def . keyType , ctx ) || isTransforming ( def . valueType , ctx ) ;
877
+ }
878
+ case "set" : {
879
+ return isTransforming ( def . valueType , ctx ) ;
880
+ }
881
+
882
+ // inner types
883
+ case "promise" :
884
+ case "optional" :
885
+ case "nonoptional" :
886
+ case "nullable" :
887
+ case "readonly" :
888
+ return isTransforming ( def . innerType , ctx ) ;
889
+ case "lazy" :
890
+ return isTransforming ( def . getter ( ) , ctx ) ;
891
+ case "default" : {
892
+ return isTransforming ( def . innerType , ctx ) ;
893
+ }
894
+ case "prefault" : {
895
+ return isTransforming ( def . innerType , ctx ) ;
896
+ }
897
+ case "custom" : {
898
+ return false ;
899
+ }
900
+ case "transform" : {
901
+ return true ;
902
+ }
903
+ case "pipe" : {
904
+ return isTransforming ( def . in , ctx ) || isTransforming ( def . out , ctx ) ;
905
+ }
906
+ case "success" : {
907
+ return false ;
908
+ }
909
+ case "catch" : {
910
+ return false ;
911
+ }
912
+
913
+ default :
914
+ def satisfies never ;
915
+ }
916
+ throw new Error ( `Unknown schema type: ${ ( def as any ) . type } ` ) ;
917
+ }
0 commit comments