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