@@ -44,7 +44,7 @@ public static JObject GenerateJsonSchema(this MethodInfo methodInfo)
4444 requiredParameters . Add ( parameter . Name ) ;
4545 }
4646
47- schema [ "properties" ] ! [ parameter . Name ] = GenerateJsonSchema ( parameter . ParameterType ) ;
47+ schema [ "properties" ] ! [ parameter . Name ] = GenerateJsonSchema ( parameter . ParameterType , schema ) ;
4848
4949 var functionParameterAttribute = parameter . GetCustomAttribute < FunctionParameterAttribute > ( ) ;
5050
@@ -62,12 +62,57 @@ public static JObject GenerateJsonSchema(this MethodInfo methodInfo)
6262 return schema ;
6363 }
6464
65- public static JObject GenerateJsonSchema ( this Type type )
65+ public static JObject GenerateJsonSchema ( this Type type , JObject rootSchema )
6666 {
6767 var schema = new JObject ( ) ;
6868 var serializer = JsonSerializer . Create ( OpenAIClient . JsonSerializationOptions ) ;
6969
70- if ( type . IsEnum )
70+ if ( ! type . IsPrimitive &&
71+ type != typeof ( Guid ) &&
72+ type != typeof ( DateTime ) &&
73+ type != typeof ( DateTimeOffset ) &&
74+ rootSchema [ "definitions" ] != null &&
75+ ( ( JObject ) rootSchema [ "definitions" ] ) . ContainsKey ( type . FullName ) )
76+ {
77+ return new JObject { [ "$ref" ] = $ "#/definitions/{ type . FullName } " } ;
78+ }
79+
80+ if ( type == typeof ( string ) )
81+ {
82+ schema [ "type" ] = "string" ;
83+ }
84+ else if ( type == typeof ( int ) ||
85+ type == typeof ( long ) ||
86+ type == typeof ( uint ) ||
87+ type == typeof ( byte ) ||
88+ type == typeof ( sbyte ) ||
89+ type == typeof ( ulong ) ||
90+ type == typeof ( short ) ||
91+ type == typeof ( ushort ) )
92+ {
93+ schema [ "type" ] = "integer" ;
94+ }
95+ else if ( type == typeof ( float ) ||
96+ type == typeof ( double ) ||
97+ type == typeof ( decimal ) )
98+ {
99+ schema [ "type" ] = "number" ;
100+ }
101+ else if ( type == typeof ( bool ) )
102+ {
103+ schema [ "type" ] = "boolean" ;
104+ }
105+ else if ( type == typeof ( DateTime ) || type == typeof ( DateTimeOffset ) )
106+ {
107+ schema [ "type" ] = "string" ;
108+ schema [ "format" ] = "date-time" ;
109+ }
110+ else if ( type == typeof ( Guid ) )
111+ {
112+ schema [ "type" ] = "string" ;
113+ schema [ "format" ] = "uuid" ;
114+ }
115+ else if ( type . IsEnum )
71116 {
72117 schema [ "type" ] = "string" ;
73118 schema [ "enum" ] = new JArray ( ) ;
@@ -80,21 +125,54 @@ public static JObject GenerateJsonSchema(this Type type)
80125 else if ( type . IsArray || ( type . IsGenericType && type . GetGenericTypeDefinition ( ) == typeof ( List < > ) ) )
81126 {
82127 schema [ "type" ] = "array" ;
83- schema [ "items" ] = GenerateJsonSchema ( type . GetElementType ( ) ?? type . GetGenericArguments ( ) [ 0 ] ) ;
128+ var elementType = type . GetElementType ( ) ?? type . GetGenericArguments ( ) [ 0 ] ;
129+
130+ if ( rootSchema [ "definitions" ] != null &&
131+ ( ( JObject ) rootSchema [ "definitions" ] ) . ContainsKey ( elementType . FullName ) )
132+ {
133+ schema [ "items" ] = new JObject { [ "$ref" ] = $ "#/definitions/{ elementType . FullName } " } ;
134+ }
135+ else
136+ {
137+ schema [ "items" ] = GenerateJsonSchema ( elementType , rootSchema ) ;
138+ }
84139 }
85- else if ( type . IsClass && type != typeof ( string ) )
140+ else
86141 {
87142 schema [ "type" ] = "object" ;
88- var properties = type . GetProperties ( ) ;
89- var propertiesInfo = new JObject ( ) ;
90- var requiredProperties = new JArray ( ) ;
143+ rootSchema [ "definitions" ] ??= new JObject ( ) ;
144+ rootSchema [ "definitions" ] [ type . FullName ] = new JObject ( ) ;
145+
146+ var properties = type . GetProperties ( BindingFlags . Public | BindingFlags . Instance | BindingFlags . DeclaredOnly ) ;
147+ var fields = type . GetFields ( BindingFlags . Public | BindingFlags . Instance | BindingFlags . DeclaredOnly ) ;
148+ var members = new List < MemberInfo > ( properties . Length + fields . Length ) ;
149+ members . AddRange ( properties ) ;
150+ members . AddRange ( fields ) ;
151+
152+ var memberInfo = new JObject ( ) ;
153+ var requiredMembers = new JArray ( ) ;
91154
92- foreach ( var property in properties )
155+ foreach ( var member in members )
93156 {
94- var propertyInfo = GenerateJsonSchema ( property . PropertyType ) ;
95- var functionPropertyAttribute = property . GetCustomAttribute < FunctionPropertyAttribute > ( ) ;
96- var jsonPropertyAttribute = property . GetCustomAttribute < JsonPropertyAttribute > ( ) ;
97- var propertyName = jsonPropertyAttribute ? . PropertyName ?? property . Name ;
157+ var memberType = GetMemberType ( member ) ;
158+ var functionPropertyAttribute = member . GetCustomAttribute < FunctionPropertyAttribute > ( ) ;
159+ var jsonPropertyAttribute = member . GetCustomAttribute < JsonPropertyAttribute > ( ) ;
160+ var propertyName = jsonPropertyAttribute ? . PropertyName ?? member . Name ;
161+
162+ // skip unity engine property for Items
163+ if ( memberType == typeof ( float ) && propertyName . Equals ( "Item" ) ) { continue ; }
164+
165+ JObject propertyInfo ;
166+
167+ if ( rootSchema [ "definitions" ] != null &&
168+ ( ( JObject ) rootSchema [ "definitions" ] ) . ContainsKey ( memberType . FullName ) )
169+ {
170+ propertyInfo = new JObject { [ "$ref" ] = $ "#/definitions/{ memberType . FullName } " } ;
171+ }
172+ else
173+ {
174+ propertyInfo = GenerateJsonSchema ( memberType , rootSchema ) ;
175+ }
98176
99177 // override properties with values from function property attribute
100178 if ( functionPropertyAttribute != null )
@@ -103,7 +181,7 @@ public static JObject GenerateJsonSchema(this Type type)
103181
104182 if ( functionPropertyAttribute . Required )
105183 {
106- requiredProperties . Add ( propertyName ) ;
184+ requiredMembers . Add ( propertyName ) ;
107185 }
108186
109187 JToken defaultValue = null ;
@@ -143,52 +221,53 @@ public static JObject GenerateJsonSchema(this Type type)
143221 propertyInfo [ "enum" ] = enums ;
144222 }
145223 }
146- else if ( Nullable . GetUnderlyingType ( property . PropertyType ) = = null )
224+ else if ( jsonPropertyAttribute ! = null )
147225 {
148- requiredProperties . Add ( propertyName ) ;
226+ switch ( jsonPropertyAttribute . Required )
227+ {
228+ case Required . Always :
229+ case Required . AllowNull :
230+ requiredMembers . Add ( propertyName ) ;
231+ break ;
232+ case Required . Default :
233+ case Required . DisallowNull :
234+ default :
235+ requiredMembers . Remove ( propertyName ) ;
236+ break ;
237+ }
238+ }
239+ else if ( Nullable . GetUnderlyingType ( memberType ) == null )
240+ {
241+ if ( member is FieldInfo )
242+ {
243+ requiredMembers . Add ( propertyName ) ;
244+ }
149245 }
150246
151- propertiesInfo [ propertyName ] = propertyInfo ;
247+ memberInfo [ propertyName ] = propertyInfo ;
152248 }
153249
154- schema [ "properties" ] = propertiesInfo ;
250+ schema [ "properties" ] = memberInfo ;
155251
156- if ( requiredProperties . Count > 0 )
157- {
158- schema [ "required" ] = requiredProperties ;
159- }
160- }
161- else
162- {
163- if ( type == typeof ( int ) || type == typeof ( long ) || type == typeof ( short ) || type == typeof ( byte ) )
252+ if ( requiredMembers . Count > 0 )
164253 {
165- schema [ "type" ] = "integer" ;
166- }
167- else if ( type == typeof ( float ) || type == typeof ( double ) || type == typeof ( decimal ) )
168- {
169- schema [ "type" ] = "number" ;
170- }
171- else if ( type == typeof ( bool ) )
172- {
173- schema [ "type" ] = "boolean" ;
174- }
175- else if ( type == typeof ( DateTime ) || type == typeof ( DateTimeOffset ) )
176- {
177- schema [ "type" ] = "string" ;
178- schema [ "format" ] = "date-time" ;
179- }
180- else if ( type == typeof ( Guid ) )
181- {
182- schema [ "type" ] = "string" ;
183- schema [ "format" ] = "uuid" ;
184- }
185- else
186- {
187- schema [ "type" ] = type . Name . ToLower ( ) ;
254+ schema [ "required" ] = requiredMembers ;
188255 }
256+
257+ rootSchema [ "definitions" ] ??= new JObject ( ) ;
258+ rootSchema [ "definitions" ] [ type . FullName ] = schema ;
259+ return new JObject { [ "$ref" ] = $ "#/definitions/{ type . FullName } " } ;
189260 }
190261
191262 return schema ;
192263 }
264+
265+ private static Type GetMemberType ( MemberInfo member )
266+ => member switch
267+ {
268+ FieldInfo fieldInfo => fieldInfo . FieldType ,
269+ PropertyInfo propertyInfo => propertyInfo . PropertyType ,
270+ _ => throw new ArgumentException ( $ "{ nameof ( MemberInfo ) } must be of type { nameof ( FieldInfo ) } , { nameof ( PropertyInfo ) } ", nameof ( member ) )
271+ } ;
193272 }
194273}
0 commit comments