Skip to content

Commit

Permalink
[Xamarin.Android.Build.Tasks] Remove use of Newtonsoft.Json (#9819)
Browse files Browse the repository at this point in the history
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).
  • Loading branch information
dellis1972 authored Feb 26, 2025
1 parent cf62814 commit 21e69b4
Show file tree
Hide file tree
Showing 6 changed files with 122 additions and 28 deletions.
1 change: 0 additions & 1 deletion build-tools/installers/create-installers.targets
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,6 @@
<_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)Mono.Options.dll" />
<_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)Mono.Options.pdb" />
<_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)MULTIDEX_JAR_LICENSE" />
<_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)Newtonsoft.Json.dll" />
<_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)NuGet.Common.dll" />
<_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)NuGet.Configuration.dll" />
<_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)NuGet.DependencyResolver.Core.dll" />
Expand Down
31 changes: 15 additions & 16 deletions src/Xamarin.Android.Build.Tasks/Tasks/BuildAppBundle.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System.Text.Json;
using System.Text.Json.Nodes;
using System;
using System.Collections.Generic;
using System.IO;
Expand Down Expand Up @@ -88,26 +88,25 @@ public override bool RunTask ()
}
}

var json = JObject.FromObject (new { });
JsonNode json = JsonNode.Parse ("{}")!;
if (!string.IsNullOrEmpty (CustomBuildConfigFile) && File.Exists (CustomBuildConfigFile)) {
using (StreamReader file = File.OpenText (CustomBuildConfigFile))
using (JsonTextReader reader = new JsonTextReader (file)) {
json = (JObject)JToken.ReadFrom(reader);
}
using Stream fs = File.OpenRead (CustomBuildConfigFile);
using JsonDocument doc = JsonDocument.Parse (fs, new JsonDocumentOptions { AllowTrailingCommas = true });
json = doc.RootElement.ToNode ();
}
var jsonAddition = JObject.FromObject (new {
var jsonAddition = new {
compression = new {
uncompressedGlob = uncompressed,
}
});

var mergeSettings = new JsonMergeSettings () {
MergeArrayHandling = MergeArrayHandling.Union,
MergeNullValueHandling = MergeNullValueHandling.Ignore
};
json.Merge (jsonAddition, mergeSettings);
Log.LogDebugMessage ("BundleConfig.json: {0}", json);
File.WriteAllText (temp, json.ToString ());

var jsonAdditionDoc = JsonSerializer.SerializeToNode (jsonAddition);

var mergedJson = json.Merge (jsonAdditionDoc);
var output = mergedJson.ToJsonString (new JsonSerializerOptions { WriteIndented = true });

Log.LogDebugMessage ("BundleConfig.json: {0}", output);
File.WriteAllText (temp, output);

//NOTE: bundletool will not overwrite
if (File.Exists (Output))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
using Microsoft.Android.Build.Tasks;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
using Newtonsoft.Json;
using System.Text.Json;
using System.Text.Json.Serialization;
using NuGet.ProjectModel;

namespace Xamarin.Android.Tasks;
Expand Down Expand Up @@ -305,7 +306,7 @@ public Project Resolve (Artifact artifact)
}
}

class MicrosoftNuGetPackageFinder
partial class MicrosoftNuGetPackageFinder
{
readonly PackageListFile? package_list;

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

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

public class PackageListFile
{
[JsonProperty ("packages")]
public List<Package>? Packages { get; set; }
}

public class Package
{
[JsonProperty ("javaId")]
public string? JavaId { get; set; }

[JsonProperty ("nugetId")]
public string? NuGetId { get; set; }
}

[JsonSourceGenerationOptions(
AllowTrailingCommas = true,
WriteIndented = true,
PropertyNameCaseInsensitive = true
)]
[JsonSerializable(typeof(PackageListFile))]
[JsonSerializable(typeof(List<Package>))]
[JsonSerializable(typeof(string))]
internal partial class PackageListFileContext : JsonSerializerContext
{
}
}

public class NuGetPackageVersionFinder
Expand Down
87 changes: 87 additions & 0 deletions src/Xamarin.Android.Build.Tasks/Utilities/JsonExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
using System;
using System.Collections.Generic;
using System.Text.Json;
using System.Text.Json.Nodes;

public static class JsonExtensions
{
public static JsonNode Merge (this JsonNode jsonBase, JsonNode jsonMerge)
{
if (jsonBase == null || jsonMerge == null)
return jsonBase;

switch (jsonBase)
{
case JsonObject jsonBaseObj when jsonMerge is JsonObject jsonMergeObj: {
var mergeNodesArray = new KeyValuePair<string, JsonNode?> [jsonMergeObj.Count];
int index = 0;
foreach (var prop in jsonMergeObj) {
mergeNodesArray [index++] = prop;
}
jsonMergeObj.Clear ();

foreach (var prop in mergeNodesArray) {
jsonBaseObj [prop.Key] = jsonBaseObj [prop.Key] switch {
JsonObject jsonBaseChildObj when prop.Value is JsonObject jsonMergeChildObj => jsonBaseChildObj.Merge (jsonMergeChildObj),
JsonArray jsonBaseChildArray when prop.Value is JsonArray jsonMergeChildArray => jsonBaseChildArray.Merge (jsonMergeChildArray),
_ => prop.Value
};
}
break;
}
case JsonArray jsonBaseArray when jsonMerge is JsonArray jsonMergeArray: {
var mergeNodesArray = new JsonNode? [jsonMergeArray.Count];
int index = 0;
foreach (var mergeNode in jsonMergeArray) {
mergeNodesArray [index++] = mergeNode;
}
jsonMergeArray.Clear ();
foreach (var mergeNode in mergeNodesArray) {
jsonBaseArray.Add (mergeNode);
}
break;
}
default:
throw new ArgumentException ($"The JsonNode type [{jsonBase.GetType ().Name}] is incompatible for merging with the target/base " +
$"type [{jsonMerge.GetType ().Name}]; merge requires the types to be the same.");
}
return jsonBase;
}

public static JsonNode? ToNode (this JsonElement element)
{
switch (element.ValueKind) {
case JsonValueKind.Object:
var obj = new JsonObject ();
foreach (JsonProperty prop in element.EnumerateObject()) {
obj [prop.Name] = prop.Value.ToNode ();
}
return obj;

case JsonValueKind.Array:
var arr = new JsonArray();
foreach (JsonElement item in element.EnumerateArray ()) {
arr.Add (item.ToNode ());
}
return arr;

case JsonValueKind.String:
return element.GetString ();

case JsonValueKind.Number:
return element.TryGetInt32 (out int intValue) ? intValue : element.GetDouble ();

case JsonValueKind.True:
return true;

case JsonValueKind.False:
return false;

case JsonValueKind.Null:
return null;

default:
throw new NotSupportedException ($"Unsupported JSON value kind: {element.ValueKind}");
}
}
}
7 changes: 4 additions & 3 deletions src/Xamarin.Android.Build.Tasks/Utilities/MamJsonParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,11 @@ public XElement ToXml ()
GetReplacementMethods ());
}

static JsonObject ReadJson (string path)
static JsonNode ReadJson (string path)
{
using (var f = File.OpenRead (path)) {
return JsonNode.Parse (f)!.AsObject ();
using (var fs = File.OpenRead (path)) {
using JsonDocument doc = JsonDocument.Parse (fs, new JsonDocumentOptions { AllowTrailingCommas = true });
return doc.RootElement.ToNode () ?? JsonNode.Parse ("{}")!;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@
<PackageReference Include="Mono.Cecil" Version="$(MonoCecilVersion)" GeneratePathProperty="true" />
<PackageReference Include="Irony" />
<PackageReference Include="NuGet.ProjectModel" Version="6.13.1" />
<PackageReference Include="Newtonsoft.Json" Version="$(NewtonsoftJsonPackageVersion)" />
<PackageReference Include="System.CodeDom" />
<PackageReference Include="System.IO.Hashing" Version="$(SystemIOHashingPackageVersion)" />
<PackageReference Include="System.Reflection.Metadata" Version="8.0.0" />
Expand Down

0 comments on commit 21e69b4

Please sign in to comment.