Skip to content

Commit 21e69b4

Browse files
authored
[Xamarin.Android.Build.Tasks] Remove use of Newtonsoft.Json (#9819)
Fixes: #9229 Remove use of Newtonsoft.Json from `Xamarin.Android.Build.Tasks.dll`. Notes: * `JsonElement` is read only. This is why we convert them to `JsonNode`. * `JsonNode.Parse()` does NOT have the same features as `JsonDocument.Parse()`, e.g you CANNOT tell it to ignore trailing comma's; it will just throw an exception. This is the reason why we have to load the `.json` via `JsonDocument` and then call the extension method `.ToNode ()` on the Root `JsonElement` to convert it to a `JsonNode`. (We use `JsonNode` to do the merging).
1 parent cf62814 commit 21e69b4

File tree

6 files changed

+122
-28
lines changed

6 files changed

+122
-28
lines changed

build-tools/installers/create-installers.targets

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,6 @@
116116
<_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)Mono.Options.dll" />
117117
<_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)Mono.Options.pdb" />
118118
<_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)MULTIDEX_JAR_LICENSE" />
119-
<_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)Newtonsoft.Json.dll" />
120119
<_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)NuGet.Common.dll" />
121120
<_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)NuGet.Configuration.dll" />
122121
<_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)NuGet.DependencyResolver.Core.dll" />

src/Xamarin.Android.Build.Tasks/Tasks/BuildAppBundle.cs

Lines changed: 15 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
using Microsoft.Build.Framework;
22
using Microsoft.Build.Utilities;
3-
using Newtonsoft.Json;
4-
using Newtonsoft.Json.Linq;
3+
using System.Text.Json;
4+
using System.Text.Json.Nodes;
55
using System;
66
using System.Collections.Generic;
77
using System.IO;
@@ -88,26 +88,25 @@ public override bool RunTask ()
8888
}
8989
}
9090

91-
var json = JObject.FromObject (new { });
91+
JsonNode json = JsonNode.Parse ("{}")!;
9292
if (!string.IsNullOrEmpty (CustomBuildConfigFile) && File.Exists (CustomBuildConfigFile)) {
93-
using (StreamReader file = File.OpenText (CustomBuildConfigFile))
94-
using (JsonTextReader reader = new JsonTextReader (file)) {
95-
json = (JObject)JToken.ReadFrom(reader);
96-
}
93+
using Stream fs = File.OpenRead (CustomBuildConfigFile);
94+
using JsonDocument doc = JsonDocument.Parse (fs, new JsonDocumentOptions { AllowTrailingCommas = true });
95+
json = doc.RootElement.ToNode ();
9796
}
98-
var jsonAddition = JObject.FromObject (new {
97+
var jsonAddition = new {
9998
compression = new {
10099
uncompressedGlob = uncompressed,
101100
}
102-
});
103-
104-
var mergeSettings = new JsonMergeSettings () {
105-
MergeArrayHandling = MergeArrayHandling.Union,
106-
MergeNullValueHandling = MergeNullValueHandling.Ignore
107101
};
108-
json.Merge (jsonAddition, mergeSettings);
109-
Log.LogDebugMessage ("BundleConfig.json: {0}", json);
110-
File.WriteAllText (temp, json.ToString ());
102+
103+
var jsonAdditionDoc = JsonSerializer.SerializeToNode (jsonAddition);
104+
105+
var mergedJson = json.Merge (jsonAdditionDoc);
106+
var output = mergedJson.ToJsonString (new JsonSerializerOptions { WriteIndented = true });
107+
108+
Log.LogDebugMessage ("BundleConfig.json: {0}", output);
109+
File.WriteAllText (temp, output);
111110

112111
//NOTE: bundletool will not overwrite
113112
if (File.Exists (Output))

src/Xamarin.Android.Build.Tasks/Tasks/JavaDependencyVerification.cs

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@
1111
using Microsoft.Android.Build.Tasks;
1212
using Microsoft.Build.Framework;
1313
using Microsoft.Build.Utilities;
14-
using Newtonsoft.Json;
14+
using System.Text.Json;
15+
using System.Text.Json.Serialization;
1516
using NuGet.ProjectModel;
1617

1718
namespace Xamarin.Android.Tasks;
@@ -305,7 +306,7 @@ public Project Resolve (Artifact artifact)
305306
}
306307
}
307308

308-
class MicrosoftNuGetPackageFinder
309+
partial class MicrosoftNuGetPackageFinder
309310
{
310311
readonly PackageListFile? package_list;
311312

@@ -318,7 +319,7 @@ public MicrosoftNuGetPackageFinder (string? file, TaskLoggingHelper log)
318319

319320
try {
320321
var json = File.ReadAllText (file);
321-
package_list = JsonConvert.DeserializeObject<PackageListFile> (json);
322+
package_list = JsonSerializer.Deserialize<PackageListFile> (json, PackageListFileContext.Default.PackageListFile);
322323
} catch (Exception ex) {
323324
log.LogMessage ("There was an error reading 'microsoft-packages.json', Android NuGet suggestions will not be provided: {0}", ex);
324325
}
@@ -331,18 +332,26 @@ public MicrosoftNuGetPackageFinder (string? file, TaskLoggingHelper log)
331332

332333
public class PackageListFile
333334
{
334-
[JsonProperty ("packages")]
335335
public List<Package>? Packages { get; set; }
336336
}
337337

338338
public class Package
339339
{
340-
[JsonProperty ("javaId")]
341340
public string? JavaId { get; set; }
342-
343-
[JsonProperty ("nugetId")]
344341
public string? NuGetId { get; set; }
345342
}
343+
344+
[JsonSourceGenerationOptions(
345+
AllowTrailingCommas = true,
346+
WriteIndented = true,
347+
PropertyNameCaseInsensitive = true
348+
)]
349+
[JsonSerializable(typeof(PackageListFile))]
350+
[JsonSerializable(typeof(List<Package>))]
351+
[JsonSerializable(typeof(string))]
352+
internal partial class PackageListFileContext : JsonSerializerContext
353+
{
354+
}
346355
}
347356

348357
public class NuGetPackageVersionFinder
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Text.Json;
4+
using System.Text.Json.Nodes;
5+
6+
public static class JsonExtensions
7+
{
8+
public static JsonNode Merge (this JsonNode jsonBase, JsonNode jsonMerge)
9+
{
10+
if (jsonBase == null || jsonMerge == null)
11+
return jsonBase;
12+
13+
switch (jsonBase)
14+
{
15+
case JsonObject jsonBaseObj when jsonMerge is JsonObject jsonMergeObj: {
16+
var mergeNodesArray = new KeyValuePair<string, JsonNode?> [jsonMergeObj.Count];
17+
int index = 0;
18+
foreach (var prop in jsonMergeObj) {
19+
mergeNodesArray [index++] = prop;
20+
}
21+
jsonMergeObj.Clear ();
22+
23+
foreach (var prop in mergeNodesArray) {
24+
jsonBaseObj [prop.Key] = jsonBaseObj [prop.Key] switch {
25+
JsonObject jsonBaseChildObj when prop.Value is JsonObject jsonMergeChildObj => jsonBaseChildObj.Merge (jsonMergeChildObj),
26+
JsonArray jsonBaseChildArray when prop.Value is JsonArray jsonMergeChildArray => jsonBaseChildArray.Merge (jsonMergeChildArray),
27+
_ => prop.Value
28+
};
29+
}
30+
break;
31+
}
32+
case JsonArray jsonBaseArray when jsonMerge is JsonArray jsonMergeArray: {
33+
var mergeNodesArray = new JsonNode? [jsonMergeArray.Count];
34+
int index = 0;
35+
foreach (var mergeNode in jsonMergeArray) {
36+
mergeNodesArray [index++] = mergeNode;
37+
}
38+
jsonMergeArray.Clear ();
39+
foreach (var mergeNode in mergeNodesArray) {
40+
jsonBaseArray.Add (mergeNode);
41+
}
42+
break;
43+
}
44+
default:
45+
throw new ArgumentException ($"The JsonNode type [{jsonBase.GetType ().Name}] is incompatible for merging with the target/base " +
46+
$"type [{jsonMerge.GetType ().Name}]; merge requires the types to be the same.");
47+
}
48+
return jsonBase;
49+
}
50+
51+
public static JsonNode? ToNode (this JsonElement element)
52+
{
53+
switch (element.ValueKind) {
54+
case JsonValueKind.Object:
55+
var obj = new JsonObject ();
56+
foreach (JsonProperty prop in element.EnumerateObject()) {
57+
obj [prop.Name] = prop.Value.ToNode ();
58+
}
59+
return obj;
60+
61+
case JsonValueKind.Array:
62+
var arr = new JsonArray();
63+
foreach (JsonElement item in element.EnumerateArray ()) {
64+
arr.Add (item.ToNode ());
65+
}
66+
return arr;
67+
68+
case JsonValueKind.String:
69+
return element.GetString ();
70+
71+
case JsonValueKind.Number:
72+
return element.TryGetInt32 (out int intValue) ? intValue : element.GetDouble ();
73+
74+
case JsonValueKind.True:
75+
return true;
76+
77+
case JsonValueKind.False:
78+
return false;
79+
80+
case JsonValueKind.Null:
81+
return null;
82+
83+
default:
84+
throw new NotSupportedException ($"Unsupported JSON value kind: {element.ValueKind}");
85+
}
86+
}
87+
}

src/Xamarin.Android.Build.Tasks/Utilities/MamJsonParser.cs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,10 +57,11 @@ public XElement ToXml ()
5757
GetReplacementMethods ());
5858
}
5959

60-
static JsonObject ReadJson (string path)
60+
static JsonNode ReadJson (string path)
6161
{
62-
using (var f = File.OpenRead (path)) {
63-
return JsonNode.Parse (f)!.AsObject ();
62+
using (var fs = File.OpenRead (path)) {
63+
using JsonDocument doc = JsonDocument.Parse (fs, new JsonDocumentOptions { AllowTrailingCommas = true });
64+
return doc.RootElement.ToNode () ?? JsonNode.Parse ("{}")!;
6465
}
6566
}
6667

src/Xamarin.Android.Build.Tasks/Xamarin.Android.Build.Tasks.csproj

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@
2929
<PackageReference Include="Mono.Cecil" Version="$(MonoCecilVersion)" GeneratePathProperty="true" />
3030
<PackageReference Include="Irony" />
3131
<PackageReference Include="NuGet.ProjectModel" Version="6.13.1" />
32-
<PackageReference Include="Newtonsoft.Json" Version="$(NewtonsoftJsonPackageVersion)" />
3332
<PackageReference Include="System.CodeDom" />
3433
<PackageReference Include="System.IO.Hashing" Version="$(SystemIOHashingPackageVersion)" />
3534
<PackageReference Include="System.Reflection.Metadata" Version="8.0.0" />

0 commit comments

Comments
 (0)