Skip to content

Commit 85c224a

Browse files
committed
System.Text.Json Migration - Adding code to parse the Project.Assets.Json file using STJ. (#5558)
Update the LockFileFormat class to use STJ for parsing the assets file.
1 parent 5382b2b commit 85c224a

25 files changed

+1876
-577
lines changed

src/NuGet.Core/NuGet.ProjectModel/JsonPackageSpecReader.Utf8JsonStreamReader.cs

Lines changed: 24 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -112,19 +112,7 @@ public partial class JsonPackageSpecReader
112112
internal static PackageSpec GetPackageSpecUtf8JsonStreamReader(Stream stream, string name, string packageSpecPath, string snapshotValue)
113113
{
114114
var reader = new Utf8JsonStreamReader(stream);
115-
PackageSpec packageSpec;
116-
packageSpec = GetPackageSpec(ref reader, name, packageSpecPath, snapshotValue);
117-
118-
if (!string.IsNullOrEmpty(name))
119-
{
120-
packageSpec.Name = name;
121-
if (!string.IsNullOrEmpty(packageSpecPath))
122-
{
123-
packageSpec.FilePath = Path.GetFullPath(packageSpecPath);
124-
125-
}
126-
}
127-
return packageSpec;
115+
return GetPackageSpec(ref reader, name, packageSpecPath, snapshotValue);
128116
}
129117

130118
internal static PackageSpec GetPackageSpec(ref Utf8JsonStreamReader jsonReader, string name, string packageSpecPath, string snapshotValue)
@@ -277,9 +265,10 @@ internal static PackageSpec GetPackageSpec(ref Utf8JsonStreamReader jsonReader,
277265

278266
internal static void ReadCentralTransitiveDependencyGroup(
279267
ref Utf8JsonStreamReader jsonReader,
280-
IList<LibraryDependency> results,
268+
out IList<LibraryDependency> results,
281269
string packageSpecPath)
282270
{
271+
results = null;
283272
if (jsonReader.Read() && jsonReader.TokenType == JsonTokenType.StartObject)
284273
{
285274
while (jsonReader.Read() && jsonReader.TokenType == JsonTokenType.PropertyName)
@@ -295,10 +284,12 @@ internal static void ReadCentralTransitiveDependencyGroup(
295284
if (jsonReader.Read())
296285
{
297286
var libraryDependency = ReadLibraryDependency(ref jsonReader, packageSpecPath, libraryName);
287+
results ??= [];
298288
results.Add(libraryDependency);
299289
}
300290
}
301291
}
292+
results ??= Array.Empty<LibraryDependency>();
302293
}
303294

304295
private static LibraryDependency ReadLibraryDependency(ref Utf8JsonStreamReader jsonReader, string packageSpecPath, string libraryName)
@@ -733,10 +724,15 @@ private static void ReadDownloadDependencies(
733724
packageSpecPath);
734725
}
735726

736-
string[] versions = versionValue.Split(VersionSeparators, StringSplitOptions.RemoveEmptyEntries);
727+
var versions = new LazyStringSplit(versionValue, VersionSeparator);
737728

738729
foreach (string singleVersionValue in versions)
739730
{
731+
if (string.IsNullOrEmpty(singleVersionValue))
732+
{
733+
continue;
734+
}
735+
740736
try
741737
{
742738
VersionRange version = VersionRange.Parse(singleVersionValue);
@@ -938,7 +934,7 @@ private static void ReadMSBuildMetadata(ref Utf8JsonStreamReader jsonReader, Pac
938934
RestoreLockProperties restoreLockProperties = null;
939935
var skipContentFileWrite = false;
940936
List<PackageSource> sources = null;
941-
List<ProjectRestoreMetadataFrameworkInfo> targetFrameworks = null;
937+
IList<ProjectRestoreMetadataFrameworkInfo> targetFrameworks = null;
942938
var validateRuntimeAssets = false;
943939
WarningProperties warningProperties = null;
944940
RestoreAuditProperties auditProperties = null;
@@ -1294,7 +1290,7 @@ private static void ReadPackageTypes(PackageSpec packageSpec, ref Utf8JsonStream
12941290
packageTypes = new[] { packageType };
12951291
break;
12961292
case JsonTokenType.StartArray:
1297-
var types = new List<PackageType>();
1293+
List<PackageType> types = null;
12981294

12991295
while (jsonReader.Read() && jsonReader.TokenType != JsonTokenType.EndArray)
13001296
{
@@ -1309,8 +1305,10 @@ private static void ReadPackageTypes(PackageSpec packageSpec, ref Utf8JsonStream
13091305
}
13101306

13111307
packageType = CreatePackageType(ref jsonReader);
1308+
types ??= [];
13121309
types.Add(packageType);
13131310
}
1311+
13141312
packageTypes = types;
13151313
break;
13161314
case JsonTokenType.Null:
@@ -1541,14 +1539,14 @@ private static RuntimeDescription ReadRuntimeDescription(ref Utf8JsonStreamReade
15411539

15421540
private static List<RuntimeDescription> ReadRuntimes(ref Utf8JsonStreamReader jsonReader)
15431541
{
1544-
var runtimeDescriptions = new List<RuntimeDescription>();
1542+
List<RuntimeDescription> runtimeDescriptions = null;
15451543

15461544
if (jsonReader.Read() && jsonReader.TokenType == JsonTokenType.StartObject)
15471545
{
15481546
while (jsonReader.Read() && jsonReader.TokenType == JsonTokenType.PropertyName)
15491547
{
15501548
RuntimeDescription runtimeDescription = ReadRuntimeDescription(ref jsonReader, jsonReader.GetString());
1551-
1549+
runtimeDescriptions ??= [];
15521550
runtimeDescriptions.Add(runtimeDescription);
15531551
}
15541552
}
@@ -1572,14 +1570,15 @@ private static void ReadScripts(ref Utf8JsonStreamReader jsonReader, PackageSpec
15721570
}
15731571
else if (jsonReader.TokenType == JsonTokenType.StartArray)
15741572
{
1575-
var list = new List<string>();
1573+
IList<string> list = null;
15761574

15771575
while (jsonReader.Read() && jsonReader.TokenType == JsonTokenType.String)
15781576
{
1577+
list ??= [];
15791578
list.Add(jsonReader.GetString());
15801579
}
15811580

1582-
packageSpec.Scripts[propertyName] = list;
1581+
packageSpec.Scripts[propertyName] = list ?? Enumerable.Empty<string>();
15831582
}
15841583
else
15851584
{
@@ -1594,15 +1593,15 @@ private static void ReadScripts(ref Utf8JsonStreamReader jsonReader, PackageSpec
15941593

15951594
private static List<CompatibilityProfile> ReadSupports(ref Utf8JsonStreamReader jsonReader)
15961595
{
1597-
var compatibilityProfiles = new List<CompatibilityProfile>();
1596+
List<CompatibilityProfile> compatibilityProfiles = null;
15981597

15991598
if (jsonReader.Read() && jsonReader.TokenType == JsonTokenType.StartObject)
16001599
{
16011600
while (jsonReader.Read() && jsonReader.TokenType == JsonTokenType.PropertyName)
16021601
{
16031602
var propertyName = jsonReader.GetString();
16041603
CompatibilityProfile compatibilityProfile = ReadCompatibilityProfile(ref jsonReader, propertyName);
1605-
1604+
compatibilityProfiles ??= [];
16061605
compatibilityProfiles.Add(compatibilityProfile);
16071606
}
16081607
}
@@ -1640,7 +1639,7 @@ private static LibraryDependencyTarget ReadTarget(
16401639

16411640
private static List<ProjectRestoreMetadataFrameworkInfo> ReadTargetFrameworks(ref Utf8JsonStreamReader jsonReader)
16421641
{
1643-
var targetFrameworks = new List<ProjectRestoreMetadataFrameworkInfo>();
1642+
List<ProjectRestoreMetadataFrameworkInfo> targetFrameworks = null;
16441643

16451644
if (jsonReader.Read() && jsonReader.TokenType == JsonTokenType.StartObject)
16461645
{
@@ -1723,7 +1722,7 @@ private static List<ProjectRestoreMetadataFrameworkInfo> ReadTargetFrameworks(re
17231722
jsonReader.Skip();
17241723
}
17251724
}
1726-
1725+
targetFrameworks ??= [];
17271726
targetFrameworks.Add(frameworkGroup);
17281727
}
17291728
}

src/NuGet.Core/NuGet.ProjectModel/JsonPackageSpecReader.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ public static partial class JsonPackageSpecReader
2323
{
2424
private static readonly char[] DelimitedStringSeparators = { ' ', ',' };
2525
private static readonly char[] VersionSeparators = new[] { ';' };
26+
private const char VersionSeparator = ';';
2627
public static readonly string RestoreOptions = "restore";
2728
public static readonly string RestoreSettings = "restoreSettings";
2829
public static readonly string HideWarningsAndErrors = "hideWarningsAndErrors";
@@ -75,10 +76,9 @@ internal static PackageSpec GetPackageSpec(JsonTextReader jsonReader, string pac
7576
return GetPackageSpec(jsonReader, name: null, packageSpecPath, snapshotValue: null, EnvironmentVariableWrapper.Instance);
7677
}
7778

78-
internal static PackageSpec GetPackageSpec(Stream stream, string name, string packageSpecPath, string snapshotValue, IEnvironmentVariableReader environmentVariableReader)
79+
internal static PackageSpec GetPackageSpec(Stream stream, string name, string packageSpecPath, string snapshotValue, IEnvironmentVariableReader environmentVariableReader, bool bypassCache = false)
7980
{
80-
var useNj = environmentVariableReader.GetEnvironmentVariable("NUGET_EXPERIMENTAL_USE_NJ_FOR_FILE_PARSING");
81-
if (string.IsNullOrEmpty(useNj) || useNj.Equals("false", StringComparison.OrdinalIgnoreCase))
81+
if (!JsonUtility.UseNewtonsoftJsonForParsing(environmentVariableReader, bypassCache))
8282
{
8383
return GetPackageSpecUtf8JsonStreamReader(stream, name, packageSpecPath, snapshotValue);
8484
}

src/NuGet.Core/NuGet.ProjectModel/JsonUtility.cs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,16 @@
66
using System.IO;
77
using Newtonsoft.Json;
88
using Newtonsoft.Json.Linq;
9+
using NuGet.Common;
910
using NuGet.Packaging.Core;
1011
using NuGet.Versioning;
1112

1213
namespace NuGet.ProjectModel
1314
{
1415
internal static class JsonUtility
1516
{
17+
internal static string NUGET_EXPERIMENTAL_USE_NJ_FOR_FILE_PARSING = nameof(NUGET_EXPERIMENTAL_USE_NJ_FOR_FILE_PARSING);
18+
internal static bool? UseNewtonsoftJson = null;
1619
internal static readonly char[] PathSplitChars = new[] { LockFile.DirectorySeparatorChar };
1720

1821
/// <summary>
@@ -43,6 +46,12 @@ internal static JObject LoadJson(TextReader reader)
4346
}
4447
}
4548

49+
internal static T LoadJson<T>(Stream stream, IUtf8JsonStreamReaderConverter<T> converter)
50+
{
51+
var streamingJsonReader = new Utf8JsonStreamReader(stream);
52+
return converter.Read(ref streamingJsonReader);
53+
}
54+
4655
internal static PackageDependency ReadPackageDependency(string property, JToken json)
4756
{
4857
var versionStr = json.Value<string>();
@@ -51,6 +60,23 @@ internal static PackageDependency ReadPackageDependency(string property, JToken
5160
versionStr == null ? null : VersionRange.Parse(versionStr));
5261
}
5362

63+
internal static bool UseNewtonsoftJsonForParsing(IEnvironmentVariableReader environmentVariableReader, bool bypassCache)
64+
{
65+
if (!UseNewtonsoftJson.HasValue || bypassCache)
66+
{
67+
if (bool.TryParse(environmentVariableReader.GetEnvironmentVariable(NUGET_EXPERIMENTAL_USE_NJ_FOR_FILE_PARSING), out var useNj))
68+
{
69+
UseNewtonsoftJson = useNj;
70+
}
71+
else
72+
{
73+
UseNewtonsoftJson = false;
74+
}
75+
}
76+
77+
return UseNewtonsoftJson.Value;
78+
}
79+
5480
internal static JProperty WritePackageDependencyWithLegacyString(PackageDependency item)
5581
{
5682
return new JProperty(
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
#nullable enable
4+
5+
using System;
6+
using System.Collections;
7+
using System.Collections.Generic;
8+
using System.Linq;
9+
10+
namespace NuGet.ProjectModel
11+
{
12+
/// <summary>
13+
/// Splits a string by a delimiter, producing substrings lazily during enumeration.
14+
/// Skips empty items, behaving equivalently to <see cref="string.Split(char[])"/> with
15+
/// <see cref="StringSplitOptions.RemoveEmptyEntries"/>.
16+
/// </summary>
17+
/// <remarks>
18+
/// Unlike <see cref="string.Split(char[])"/> and overloads, <see cref="LazyStringSplit"/>
19+
/// does not allocate an array for the return, and allocates strings on demand during
20+
/// enumeration. A custom enumerator type is used so that the only allocations made are
21+
/// the substrings themselves. We also avoid the large internal arrays assigned by the
22+
/// methods on <see cref="string"/>.
23+
/// </remarks>
24+
internal readonly struct LazyStringSplit : IEnumerable<string>
25+
{
26+
private readonly string _input;
27+
private readonly char _delimiter;
28+
29+
public LazyStringSplit(string input, char delimiter)
30+
{
31+
if (input is null)
32+
{
33+
throw new ArgumentNullException(nameof(input));
34+
}
35+
36+
_input = input;
37+
_delimiter = delimiter;
38+
}
39+
40+
public Enumerator GetEnumerator() => new(this);
41+
42+
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
43+
44+
IEnumerator<string> IEnumerable<string>.GetEnumerator() => GetEnumerator();
45+
46+
public IEnumerable<T> Select<T>(Func<string, T> func)
47+
{
48+
foreach (string value in this)
49+
{
50+
yield return func(value);
51+
}
52+
}
53+
54+
public string First()
55+
{
56+
return FirstOrDefault() ?? throw new InvalidOperationException("Sequence is empty.");
57+
}
58+
59+
public string? FirstOrDefault()
60+
{
61+
var enumerator = new Enumerator(this);
62+
return enumerator.MoveNext() ? enumerator.Current : null;
63+
}
64+
65+
public struct Enumerator : IEnumerator<string>
66+
{
67+
private readonly string _input;
68+
private readonly char _delimiter;
69+
private int _index;
70+
71+
internal Enumerator(in LazyStringSplit split)
72+
{
73+
_index = 0;
74+
_input = split._input;
75+
_delimiter = split._delimiter;
76+
Current = null!;
77+
}
78+
79+
public string Current { get; private set; }
80+
81+
public bool MoveNext()
82+
{
83+
while (_index != _input.Length)
84+
{
85+
int delimiterIndex = _input.IndexOf(_delimiter, _index);
86+
87+
if (delimiterIndex == -1)
88+
{
89+
Current = _input.Substring(_index);
90+
_index = _input.Length;
91+
return true;
92+
}
93+
94+
int length = delimiterIndex - _index;
95+
96+
if (length == 0)
97+
{
98+
_index++;
99+
continue;
100+
}
101+
102+
Current = _input.Substring(_index, length);
103+
_index = delimiterIndex + 1;
104+
return true;
105+
}
106+
107+
return false;
108+
}
109+
110+
object IEnumerator.Current => Current;
111+
112+
void IEnumerator.Reset()
113+
{
114+
_index = 0;
115+
Current = null!;
116+
}
117+
118+
void IDisposable.Dispose() { }
119+
}
120+
}
121+
122+
internal static class LazyStringSplitExtensions
123+
{
124+
/// <remarks>
125+
/// This extension method has special knowledge of the <see cref="LazyStringSplit"/> type and
126+
/// can compute its result without allocation.
127+
/// </remarks>
128+
/// <inheritdoc cref="Enumerable.FirstOrDefault{TSource}(IEnumerable{TSource})"/>
129+
public static string? FirstOrDefault(this LazyStringSplit lazyStringSplit)
130+
{
131+
LazyStringSplit.Enumerator enumerator = lazyStringSplit.GetEnumerator();
132+
133+
return enumerator.MoveNext()
134+
? enumerator.Current
135+
: null;
136+
}
137+
}
138+
}

0 commit comments

Comments
 (0)