Skip to content

Commit 20067e0

Browse files
Lower AIJsonUtilities to STJv8 and move to Abstractions library.
1 parent 0ce85d2 commit 20067e0

20 files changed

+4283
-17
lines changed

eng/MSBuild/LegacySupport.props

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,10 @@
4343
<Compile Include="$(MSBuildThisFileDirectory)\..\..\src\LegacySupport\StringSyntaxAttribute\*.cs" LinkBase="LegacySupport\StringSyntaxAttribute" />
4444
</ItemGroup>
4545

46+
<ItemGroup Condition="'$(InjectJsonSchemaExporterOnLegacy)' == 'true' AND ('$(TargetFramework)' == 'net462' or '$(TargetFramework)' == 'netstandard2.0' or '$(TargetFramework)' == 'netcoreapp3.1' or '$(TargetFramework)' == 'net6.0' or '$(TargetFramework)' == 'net7.0' or '$(TargetFramework)' == 'net8.0')">
47+
<Compile Include="$(MSBuildThisFileDirectory)\..\..\src\Shared\JsonSchemaExporter\**\*.cs" LinkBase="Shared\EmptyCollections" />
48+
</ItemGroup>
49+
4650
<ItemGroup Condition="'$(InjectGetOrAddOnLegacy)' == 'true' AND ('$(TargetFramework)' == 'net462' or '$(TargetFramework)' == 'netstandard2.0')">
4751
<Compile Include="$(MSBuildThisFileDirectory)\..\..\src\LegacySupport\GetOrAdd\*.cs" LinkBase="LegacySupport\GetOrAdd" />
4852
</ItemGroup>

eng/packages/TestOnly.props

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
<PackageVersion Include="Verify.Xunit" Version="20.4.0" />
2121
<PackageVersion Include="Xunit.Combinatorial" Version="1.6.24" />
2222
<PackageVersion Include="xunit.extensibility.execution" Version="2.4.2" />
23+
<PackageVersion Include="JsonSchema.Net" Version="7.2.3" />
24+
<PackageVersion Include="JsonSchema.Net.Generation" Version="4.3.0.2" />
2325
</ItemGroup>
2426

2527
<ItemGroup Condition="'$(TargetFramework)' == 'net462'">

src/Libraries/Microsoft.Extensions.AI.Abstractions/Microsoft.Extensions.AI.Abstractions.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
</PropertyGroup>
2020

2121
<PropertyGroup>
22+
<InjectJsonSchemaExporterOnLegacy>true</InjectJsonSchemaExporterOnLegacy>
2223
<InjectSharedEmptyCollections>true</InjectSharedEmptyCollections>
2324
<InjectStringHashOnLegacy>true</InjectStringHashOnLegacy>
2425
<InjectStringSyntaxAttributeOnLegacy>true</InjectStringSyntaxAttributeOnLegacy>

src/Libraries/Microsoft.Extensions.AI/Utilities/AIJsonUtilities.Schema.cs renamed to src/Libraries/Microsoft.Extensions.AI.Abstractions/Utilities/AIJsonUtilities.Schema.cs

Lines changed: 70 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@
55
using System.Collections.Concurrent;
66
using System.ComponentModel;
77
using System.Diagnostics;
8+
#if !NET9_0_OR_GREATER
9+
using System.Diagnostics.CodeAnalysis;
10+
#endif
811
using System.Linq;
912
using System.Reflection;
1013
using System.Runtime.CompilerServices;
@@ -16,6 +19,7 @@
1619
#pragma warning disable S1121 // Assignments should not be made from within sub-expressions
1720
#pragma warning disable S107 // Methods should not have too many parameters
1821
#pragma warning disable S1075 // URIs should not be hardcoded
22+
#pragma warning disable SA1118 // Parameter should not span multiple lines
1923

2024
using FunctionParameterKey = (
2125
System.Type? Type,
@@ -176,6 +180,11 @@ private static JsonElement GetJsonSchemaCached(JsonSerializerOptions options, Fu
176180
#endif
177181
}
178182

183+
#if !NET9_0_OR_GREATER
184+
[UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access",
185+
Justification = "Pre STJ-9 schema extraction can fail with a runtime exception if certain reflection metadata have been trimmed. " +
186+
"The exception message will guide users to turn off 'IlcTrimMetadata' which resolves all issues.")]
187+
#endif
179188
private static JsonElement GetJsonSchemaCore(JsonSerializerOptions options, FunctionParameterKey key)
180189
{
181190
_ = Throw.IfNull(options);
@@ -238,16 +247,9 @@ JsonNode TransformSchemaNode(JsonSchemaExporterContext ctx, JsonNode schema)
238247
const string DefaultPropertyName = "default";
239248
const string RefPropertyName = "$ref";
240249

241-
// Find the first DescriptionAttribute, starting first from the property, then the parameter, and finally the type itself.
242-
Type descAttrType = typeof(DescriptionAttribute);
243-
var descriptionAttribute =
244-
GetAttrs(descAttrType, ctx.PropertyInfo?.AttributeProvider)?.FirstOrDefault() ??
245-
GetAttrs(descAttrType, ctx.PropertyInfo?.AssociatedParameter?.AttributeProvider)?.FirstOrDefault() ??
246-
GetAttrs(descAttrType, ctx.TypeInfo.Type)?.FirstOrDefault();
247-
248-
if (descriptionAttribute is DescriptionAttribute attr)
250+
if (ctx.ResolveAttribute<DescriptionAttribute>() is { } attr)
249251
{
250-
ConvertSchemaToObject(ref schema).Insert(0, DescriptionPropertyName, (JsonNode)attr.Description);
252+
ConvertSchemaToObject(ref schema).InsertAtStart(DescriptionPropertyName, (JsonNode)attr.Description);
251253
}
252254

253255
if (schema is JsonObject objSchema)
@@ -270,7 +272,7 @@ JsonNode TransformSchemaNode(JsonSchemaExporterContext ctx, JsonNode schema)
270272
// Include the type keyword in enum types
271273
if (key.IncludeTypeInEnumSchemas && ctx.TypeInfo.Type.IsEnum && objSchema.ContainsKey(EnumPropertyName) && !objSchema.ContainsKey(TypePropertyName))
272274
{
273-
objSchema.Insert(0, TypePropertyName, "string");
275+
objSchema.InsertAtStart(TypePropertyName, "string");
274276
}
275277

276278
// Disallow additional properties in object schemas
@@ -305,7 +307,7 @@ JsonNode TransformSchemaNode(JsonSchemaExporterContext ctx, JsonNode schema)
305307
if (index < 0)
306308
{
307309
// If there's no description property, insert it at the beginning of the doc.
308-
obj.Insert(0, DescriptionPropertyName, (JsonNode)key.Description!);
310+
obj.InsertAtStart(DescriptionPropertyName, (JsonNode)key.Description!);
309311
}
310312
else
311313
{
@@ -323,15 +325,12 @@ JsonNode TransformSchemaNode(JsonSchemaExporterContext ctx, JsonNode schema)
323325
if (key.IncludeSchemaUri)
324326
{
325327
// The $schema property must be the first keyword in the object
326-
ConvertSchemaToObject(ref schema).Insert(0, SchemaPropertyName, (JsonNode)SchemaKeywordUri);
328+
ConvertSchemaToObject(ref schema).InsertAtStart(SchemaPropertyName, (JsonNode)SchemaKeywordUri);
327329
}
328330
}
329331

330332
return schema;
331333

332-
static object[]? GetAttrs(Type attrType, ICustomAttributeProvider? provider) =>
333-
provider?.GetCustomAttributes(attrType, inherit: false);
334-
335334
static JsonObject ConvertSchemaToObject(ref JsonNode schema)
336335
{
337336
JsonObject obj;
@@ -370,6 +369,62 @@ private static bool TypeIsArrayContainingInteger(JsonNode schema)
370369
return false;
371370
}
372371

372+
private static void InsertAtStart(this JsonObject jsonObject, string key, JsonNode value)
373+
{
374+
#if NET9_0_OR_GREATER
375+
jsonObject.Insert(0, key, value);
376+
#else
377+
jsonObject.Remove(key);
378+
var copiedEntries = jsonObject.ToArray();
379+
jsonObject.Clear();
380+
381+
jsonObject.Add(key, value);
382+
foreach (var entry in copiedEntries)
383+
{
384+
jsonObject[entry.Key] = entry.Value;
385+
}
386+
#endif
387+
}
388+
389+
#if !NET9_0_OR_GREATER
390+
private static int IndexOf(this JsonObject jsonObject, string key)
391+
{
392+
int i = 0;
393+
foreach (var entry in jsonObject)
394+
{
395+
if (string.Equals(entry.Key, key, StringComparison.Ordinal))
396+
{
397+
return i;
398+
}
399+
400+
i++;
401+
}
402+
403+
return -1;
404+
}
405+
#endif
406+
407+
private static TAttribute? ResolveAttribute<TAttribute>(this JsonSchemaExporterContext ctx)
408+
where TAttribute : Attribute
409+
{
410+
// Resolve attributes from locations in the following order:
411+
// 1. Property-level attributes
412+
// 2. Parameter-level attributes and
413+
// 3. Type-level attributes.
414+
return
415+
#if NET9_0_OR_GREATER
416+
GetAttrs(ctx.PropertyInfo?.AttributeProvider) ??
417+
GetAttrs(ctx.PropertyInfo?.AssociatedParameter?.AttributeProvider) ??
418+
#else
419+
GetAttrs(ctx.PropertyAttributeProvider) ??
420+
GetAttrs(ctx.ParameterInfo) ??
421+
#endif
422+
GetAttrs(ctx.TypeInfo.Type);
423+
424+
static TAttribute? GetAttrs(ICustomAttributeProvider? provider) =>
425+
(TAttribute?)provider?.GetCustomAttributes(typeof(TAttribute), inherit: false).FirstOrDefault();
426+
}
427+
373428
private static JsonElement ParseJsonElement(ReadOnlySpan<byte> utf8Json)
374429
{
375430
Utf8JsonReader reader = new(utf8Json);

0 commit comments

Comments
 (0)