private List <PackageData> GetPackagesFromManifestJson()
        {
            if (!myManifestPath.ExistsFile)
            {
                // This is not really expected, unless we're on an older Unity that doesn't support package manager
                myLogger.Info("manifest.json does not exist");
                return(null);
            }

            myLogger.Verbose("Getting packages from manifest.json");

            try
            {
                var projectManifest = ManifestJson.FromJson(myManifestPath.ReadAllText2().Text);

                // Now we've deserialised manifest.json, log why we skipped packages-lock.json
                LogWhySkippedPackagesLock(projectManifest);

                var appPath = myUnityVersion.GetActualAppPathForSolution();
                var builtInPackagesFolder = UnityInstallationFinder.GetBuiltInPackagesFolder(appPath);

                // Read the editor's default manifest, which gives us the minimum versions for various packages
                var globalManifestPath = UnityInstallationFinder.GetPackageManagerDefaultManifest(appPath);
                if (globalManifestPath.ExistsFile && myLastReadGlobalManifestPath != globalManifestPath)
                {
                    myLastReadGlobalManifestPath = globalManifestPath;
                    myGlobalManifest             = SafelyReadGlobalManifestFile(globalManifestPath);
                }

                var registry = projectManifest.Registry ?? DefaultRegistryUrl;

                var packages = new Dictionary <string, PackageData>();
                foreach (var(id, version) in projectManifest.Dependencies)
                {
                    if (version.Equals("exclude", StringComparison.OrdinalIgnoreCase))
                    {
                        continue;
                    }

                    projectManifest.Lock.TryGetValue(id, out var lockDetails);
                    packages[id] = GetPackageData(id, version, registry, builtInPackagesFolder,
                                                  lockDetails);
                }

                // From observation, Unity treats package folders in the Packages folder as actual packages, even if they're
                // not registered in manifest.json. They must have a */package.json file, in the root of the package itself
                foreach (var child in myPackagesFolder.GetChildDirectories())
                {
                    // The folder name is not reliable to act as ID, so we'll use the ID from package.json. All other
                    // packages get the ID from manifest.json or packages-lock.json. This is assumed to be the same as
                    // the ID in package.json
                    var packageData = GetPackageDataFromFolder(null, child, PackageSource.Embedded);
                    if (packageData != null)
                    {
                        packages[packageData.Id] = packageData;
                    }
                }

                // Calculate the transitive dependencies. Based on observation, we simply go with the highest available
                var packagesToProcess = new List <PackageData>(packages.Values);
                while (packagesToProcess.Count > 0)
                {
                    var foundDependencies = GetPackagesFromDependencies(registry, builtInPackagesFolder,
                                                                        packages, packagesToProcess);
                    foreach (var package in foundDependencies)
                    {
                        packages[package.Id] = package;
                    }

                    packagesToProcess = foundDependencies;
                }

                return(new List <PackageData>(packages.Values));
            }
            catch (Exception e)
            {
                myLogger.LogExceptionSilently(e);
                return(null);
            }
        }