33
44#nullable disable
55
6+ using System . Text . Encodings . Web ;
7+ using System . Text . Json ;
8+ using System . Text . Json . Nodes ;
69using Microsoft . Build . Framework ;
710using Microsoft . Build . Utilities ;
8- using Newtonsoft . Json ;
9- using Newtonsoft . Json . Linq ;
10- using Newtonsoft . Json . Serialization ;
1111using NuGet . Frameworks ;
1212using NuGet . ProjectModel ;
1313
@@ -281,13 +281,11 @@ private void AddUserRuntimeOptions(RuntimeOptions runtimeOptions)
281281 return ;
282282 }
283283
284- JObject runtimeOptionsFromProject ;
285- using ( JsonTextReader reader = new ( File . OpenText ( userConfigPath ) ) )
286- {
287- runtimeOptionsFromProject = JObject . Load ( reader ) ;
288- }
284+ JsonObject runtimeOptionsFromProject = ( JsonObject ) JsonNode . Parse (
285+ File . ReadAllText ( userConfigPath ) ,
286+ documentOptions : new JsonDocumentOptions { CommentHandling = JsonCommentHandling . Skip , AllowTrailingCommas = true } ) ;
289287
290- foreach ( var runtimeOption in runtimeOptionsFromProject )
288+ foreach ( KeyValuePair < string , JsonNode > runtimeOption in runtimeOptionsFromProject )
291289 {
292290 runtimeOptions . RawOptions . Add ( runtimeOption . Key , runtimeOption . Value ) ;
293291 }
@@ -300,45 +298,41 @@ private void AddHostConfigurationOptions(RuntimeOptions runtimeOptions)
300298 return ;
301299 }
302300
303- JObject configProperties = GetConfigProperties ( runtimeOptions ) ;
301+ JsonObject configProperties = GetConfigProperties ( runtimeOptions ) ;
304302
305303 foreach ( var hostConfigurationOption in HostConfigurationOptions )
306304 {
307305 configProperties [ hostConfigurationOption . ItemSpec ] = GetConfigPropertyValue ( hostConfigurationOption ) ;
308306 }
309307 }
310308
311- private static JObject GetConfigProperties ( RuntimeOptions runtimeOptions )
309+ private static JsonObject GetConfigProperties ( RuntimeOptions runtimeOptions )
312310 {
313- JToken configProperties ;
314- if ( ! runtimeOptions . RawOptions . TryGetValue ( "configProperties" , out configProperties )
315- || configProperties == null
316- || configProperties . Type != JTokenType . Object )
311+ if ( ! runtimeOptions . RawOptions . TryGetValue ( "configProperties" , out JsonNode configProperties )
312+ || configProperties is not JsonObject )
317313 {
318- configProperties = new JObject ( ) ;
314+ configProperties = new JsonObject ( ) ;
319315 runtimeOptions . RawOptions [ "configProperties" ] = configProperties ;
320316 }
321317
322- return ( JObject ) configProperties ;
318+ return ( JsonObject ) configProperties ;
323319 }
324320
325- private static JToken GetConfigPropertyValue ( ITaskItem hostConfigurationOption )
321+ private static JsonNode GetConfigPropertyValue ( ITaskItem hostConfigurationOption )
326322 {
327323 string valueString = hostConfigurationOption . GetMetadata ( "Value" ) ;
328324
329- bool boolValue ;
330- if ( bool . TryParse ( valueString , out boolValue ) )
325+ if ( bool . TryParse ( valueString , out bool boolValue ) )
331326 {
332- return new JValue ( boolValue ) ;
327+ return JsonValue . Create ( boolValue ) ;
333328 }
334329
335- int intValue ;
336- if ( int . TryParse ( valueString , out intValue ) )
330+ if ( int . TryParse ( valueString , out int intValue ) )
337331 {
338- return new JValue ( intValue ) ;
332+ return JsonValue . Create ( intValue ) ;
339333 }
340334
341- return new JValue ( valueString ) ;
335+ return JsonValue . Create ( valueString ) ;
342336 }
343337
344338 private void WriteDevRuntimeConfig ( IList < LockFileItem > packageFolders )
@@ -391,19 +385,115 @@ private static string EnsureNoTrailingDirectorySeparator(string path)
391385 return path ;
392386 }
393387
394- private static void WriteToJsonFile ( string fileName , object value )
388+ private static void WriteToJsonFile ( string fileName , RuntimeConfig value )
395389 {
396- JsonSerializer serializer = new ( )
390+ // Build the document explicitly with the System.Text.Json node API instead of a
391+ // reflection-based serializer, which is not trim/AOT safe (IL2026/IL3050). The writer
392+ // is configured to reproduce the previous output exactly: 2-space indentation,
393+ // environment newlines, and relaxed escaping (so characters such as '+' in paths are
394+ // written verbatim rather than as \uXXXX escapes).
395+ JsonObject json = new ( )
397396 {
398- ContractResolver = new CamelCasePropertyNamesContractResolver ( ) ,
399- Formatting = Formatting . Indented ,
400- DefaultValueHandling = DefaultValueHandling . Ignore
397+ [ "runtimeOptions" ] = SerializeRuntimeOptions ( value . RuntimeOptions )
401398 } ;
402399
403- using ( JsonTextWriter writer = new ( new StreamWriter ( File . Create ( fileName ) ) ) )
400+ JsonWriterOptions options = new ( )
401+ {
402+ Indented = true ,
403+ NewLine = Environment . NewLine ,
404+ Encoder = JavaScriptEncoder . UnsafeRelaxedJsonEscaping
405+ } ;
406+
407+ using ( FileStream stream = File . Create ( fileName ) )
408+ using ( Utf8JsonWriter writer = new ( stream , options ) )
409+ {
410+ json . WriteTo ( writer ) ;
411+ }
412+ }
413+
414+ private static JsonObject SerializeRuntimeOptions ( RuntimeOptions runtimeOptions )
415+ {
416+ // Declared properties are emitted in declaration order. Null values are omitted.
417+ JsonObject json = new ( ) ;
418+
419+ if ( runtimeOptions . Tfm != null )
420+ {
421+ json [ "tfm" ] = runtimeOptions . Tfm ;
422+ }
423+
424+ if ( runtimeOptions . RollForward != null )
425+ {
426+ json [ "rollForward" ] = runtimeOptions . RollForward ;
427+ }
428+
429+ if ( runtimeOptions . Framework != null )
430+ {
431+ json [ "framework" ] = SerializeFramework ( runtimeOptions . Framework ) ;
432+ }
433+
434+ if ( runtimeOptions . Frameworks != null )
435+ {
436+ json [ "frameworks" ] = SerializeFrameworks ( runtimeOptions . Frameworks ) ;
437+ }
438+
439+ if ( runtimeOptions . IncludedFrameworks != null )
440+ {
441+ json [ "includedFrameworks" ] = SerializeFrameworks ( runtimeOptions . IncludedFrameworks ) ;
442+ }
443+
444+ if ( runtimeOptions . AdditionalProbingPaths != null )
445+ {
446+ JsonArray probingPaths = new ( ) ;
447+ foreach ( string probingPath in runtimeOptions . AdditionalProbingPaths )
448+ {
449+ // Cast to JsonNode so the non-generic Add overload is used; the generic
450+ // JsonArray.Add<T> is annotated as trim/AOT unsafe (IL2026/IL3050).
451+ probingPaths . Add ( ( JsonNode ) probingPath ) ;
452+ }
453+
454+ json [ "additionalProbingPaths" ] = probingPaths ;
455+ }
456+
457+ // RawOptions is the extension-data bag; its entries are written as direct children of
458+ // runtimeOptions, after the declared properties. Clone each value so it can be
459+ // re-parented into this document regardless of where it originated.
460+ foreach ( KeyValuePair < string , JsonNode > rawOption in runtimeOptions . RawOptions )
404461 {
405- serializer . Serialize ( writer , value ) ;
462+ json [ rawOption . Key ] = rawOption . Value ? . DeepClone ( ) ;
406463 }
464+
465+ return json ;
466+ }
467+
468+ private static JsonArray SerializeFrameworks ( List < RuntimeConfigFramework > frameworks )
469+ {
470+ JsonArray array = new ( ) ;
471+
472+ foreach ( RuntimeConfigFramework framework in frameworks )
473+ {
474+ // Cast to JsonNode to use the non-generic Add overload (the generic
475+ // JsonArray.Add<T> is annotated as trim/AOT unsafe).
476+ array . Add ( ( JsonNode ) SerializeFramework ( framework ) ) ;
477+ }
478+
479+ return array ;
480+ }
481+
482+ private static JsonObject SerializeFramework ( RuntimeConfigFramework framework )
483+ {
484+ JsonObject json = new ( ) ;
485+
486+ if ( framework . Name != null )
487+ {
488+ json [ "name" ] = framework . Name ;
489+ }
490+
491+ if ( framework . Version != null )
492+ {
493+ json [ "version" ] = framework . Version ;
494+ }
495+
496+ return json ;
407497 }
408498 }
409499}
0 commit comments