@@ -1308,6 +1308,141 @@ public async Task SerializeUnevaluatedPropertiesTrueNotEmittedInEarlierVersions(
13081308 Assert . True ( JsonNode . DeepEquals ( JsonNode . Parse ( expected ) , JsonNode . Parse ( actual ) ) ) ;
13091309 }
13101310
1311+ // PatternProperties tests
1312+ [ Theory ]
1313+ [ InlineData ( OpenApiSpecVersion . OpenApi3_1 ) ]
1314+ public async Task SerializePatternPropertiesAsKeywordInV31AndV32 ( OpenApiSpecVersion version )
1315+ {
1316+ var expected = @"{ ""patternProperties"": { ""^[a-z]+"": { ""type"": ""string"" } } }" ;
1317+ // Given - patternProperties should be emitted as a standard keyword in v3.1+
1318+ var schema = new OpenApiSchema
1319+ {
1320+ PatternProperties = new Dictionary < string , IOpenApiSchema >
1321+ {
1322+ [ "^[a-z]+" ] = new OpenApiSchema { Type = JsonSchemaType . String }
1323+ }
1324+ } ;
1325+
1326+ // When
1327+ var actual = await schema . SerializeAsJsonAsync ( version ) ;
1328+
1329+ // Then
1330+ Assert . True ( JsonNode . DeepEquals ( JsonNode . Parse ( expected ) , JsonNode . Parse ( actual ) ) ) ;
1331+ }
1332+
1333+ [ Theory ]
1334+ [ InlineData ( OpenApiSpecVersion . OpenApi2_0 ) ]
1335+ [ InlineData ( OpenApiSpecVersion . OpenApi3_0 ) ]
1336+ public async Task SerializePatternPropertiesAsExtensionInEarlierVersions ( OpenApiSpecVersion version )
1337+ {
1338+ var expected = @"{ ""x-jsonschema-patternProperties"": { ""^[a-z]+"": { ""type"": ""string"" } } }" ;
1339+ // Given - patternProperties should be emitted as extension in versions < 3.1
1340+ var schema = new OpenApiSchema
1341+ {
1342+ PatternProperties = new Dictionary < string , IOpenApiSchema >
1343+ {
1344+ [ "^[a-z]+" ] = new OpenApiSchema { Type = JsonSchemaType . String }
1345+ }
1346+ } ;
1347+
1348+ // When
1349+ var actual = await schema . SerializeAsJsonAsync ( version ) ;
1350+
1351+ // Then
1352+ Assert . True ( JsonNode . DeepEquals ( JsonNode . Parse ( expected ) , JsonNode . Parse ( actual ) ) ) ;
1353+ }
1354+
1355+ [ Theory ]
1356+ [ InlineData ( OpenApiSpecVersion . OpenApi2_0 ) ]
1357+ [ InlineData ( OpenApiSpecVersion . OpenApi3_0 ) ]
1358+ public async Task SerializeEmptyPatternPropertiesNotEmittedInEarlierVersions ( OpenApiSpecVersion version )
1359+ {
1360+ var expected = @"{ }" ;
1361+ // Given - empty patternProperties should not emit extension
1362+ var schema = new OpenApiSchema
1363+ {
1364+ PatternProperties = new Dictionary < string , IOpenApiSchema > ( )
1365+ } ;
1366+
1367+ // When
1368+ var actual = await schema . SerializeAsJsonAsync ( version ) ;
1369+
1370+ // Then
1371+ Assert . True ( JsonNode . DeepEquals ( JsonNode . Parse ( expected ) , JsonNode . Parse ( actual ) ) ) ;
1372+ }
1373+
1374+ [ Fact ]
1375+ public void DeserializePatternPropertiesExtensionInV2AssignsPatternPropertiesProperty ( )
1376+ {
1377+ // Given - a V2 document with x-jsonschema-patternProperties extension in a definition
1378+ var jsonContent = """
1379+ {
1380+ "swagger": "2.0",
1381+ "info": { "title": "Test", "version": "1.0" },
1382+ "paths": {},
1383+ "definitions": {
1384+ "TestSchema": {
1385+ "type": "object",
1386+ "x-jsonschema-patternProperties": {
1387+ "^[a-z]+": { "type": "string" }
1388+ }
1389+ }
1390+ }
1391+ }
1392+ """ ;
1393+
1394+ // When
1395+ var readResult = OpenApiDocument . Parse ( jsonContent , "json" ) ;
1396+
1397+ // Then
1398+ Assert . Empty ( readResult . Diagnostic . Errors ) ;
1399+ var schema = readResult . Document . Components . Schemas [ "TestSchema" ] ;
1400+ Assert . NotNull ( schema ) ;
1401+ Assert . NotNull ( schema . PatternProperties ) ;
1402+ Assert . Single ( schema . PatternProperties ) ;
1403+ Assert . True ( schema . PatternProperties . ContainsKey ( "^[a-z]+" ) ) ;
1404+ Assert . Equal ( JsonSchemaType . String , schema . PatternProperties [ "^[a-z]+" ] . Type ) ;
1405+ // Extension should NOT be present on the schema (it was consumed)
1406+ Assert . True ( schema . Extensions is null || ! schema . Extensions . ContainsKey ( "x-jsonschema-patternProperties" ) ) ;
1407+ }
1408+
1409+ [ Fact ]
1410+ public void DeserializePatternPropertiesExtensionInV3AssignsPatternPropertiesProperty ( )
1411+ {
1412+ // Given - a V3 document with x-jsonschema-patternProperties extension in a component schema
1413+ var jsonContent = """
1414+ {
1415+ "openapi": "3.0.0",
1416+ "info": { "title": "Test", "version": "1.0" },
1417+ "paths": {},
1418+ "components": {
1419+ "schemas": {
1420+ "TestSchema": {
1421+ "type": "object",
1422+ "x-jsonschema-patternProperties": {
1423+ "^[a-z]+": { "type": "string" }
1424+ }
1425+ }
1426+ }
1427+ }
1428+ }
1429+ """ ;
1430+
1431+ // When
1432+ var readResult = OpenApiDocument . Parse ( jsonContent , "json" ) ;
1433+
1434+ // Then
1435+ Assert . Empty ( readResult . Diagnostic . Errors ) ;
1436+ var schema = readResult . Document . Components . Schemas [ "TestSchema" ] ;
1437+ Assert . NotNull ( schema ) ;
1438+ Assert . NotNull ( schema . PatternProperties ) ;
1439+ Assert . Single ( schema . PatternProperties ) ;
1440+ Assert . True ( schema . PatternProperties . ContainsKey ( "^[a-z]+" ) ) ;
1441+ Assert . Equal ( JsonSchemaType . String , schema . PatternProperties [ "^[a-z]+" ] . Type ) ;
1442+ // Extension should NOT be present on the schema (it was consumed)
1443+ Assert . True ( schema . Extensions is null || ! schema . Extensions . ContainsKey ( "x-jsonschema-patternProperties" ) ) ;
1444+ }
1445+
13111446 internal class SchemaVisitor : OpenApiVisitorBase
13121447 {
13131448 public List < string > Titles = new ( ) ;
0 commit comments