I see that no one has answered this question and this is still a problem as of Unity Version 2020.3.28f1. So I have created a solution, free for use, for whoever needs it:
using UnityEngine;
public class UnknownObject : ScriptableObject
{
[System.NonSerialized]
private Object _Reference;
public Object Reference
{
get => _Reference;
protected set => _Reference = value;
}
[System.NonSerialized]
private long _LocalIdentifier;
public long LocalIdentifier
{
get => _LocalIdentifier;
protected set => _LocalIdentifier = value;
}
[System.NonSerialized]
private string _Data;
public string Data
{
get => _Data;
protected set => _Data = value;
}
[System.NonSerialized]
private int _SubIndex;
public int SubIndex
{
get => _SubIndex;
protected set => _SubIndex = value;
}
[System.NonSerialized]
private System.Action<int> _OnDelete;
public System.Action<int> OnDelete
{
get => _OnDelete;
protected set => _OnDelete = value;
}
private void OnDestroy()
{
OnDelete(SubIndex);
}
public static UnknownObject CreateInstance(long localIdentifier, string data, Object reference, int subIndex, System.Action<int> onDelete)
{
var instance = CreateInstance<UnknownObject>();
instance.LocalIdentifier = localIdentifier;
instance.Data = data;
instance.Reference = reference;
instance.SubIndex = subIndex;
instance.OnDelete = onDelete;
return instance;
}
}
using UnityEngine;
using UnityEditor;
using System.Linq;
using System.Text;
using System.IO;
using System.Collections.Generic;
using System.Text.RegularExpressions;
public class AssetDatabaseHelper
{
public readonly static Regex LocalIdPattern = new Regex(@"---\s!.!\d+\s&(.*)");
private static (long, string)[] ParseSubAssetsAtIndex(Object mainAsset, int[] indices, bool keepMain = false)
{
var ownerPath = AssetDatabase.GetAssetPath(mainAsset);
_ = AssetDatabase.TryGetGUIDAndLocalFileIdentifier(mainAsset, out _, out long mainAssetLocalId);
var yaml = new List<(long, StringBuilder)>();
var index = indices?[0];
using (var sr = new StreamReader(ownerPath))
{
var line = string.Empty;
var delete = false;
var subAssetIndex = 0;
var localId = 0L;
while ((line = sr.ReadLine()) != null)
{
var match = LocalIdPattern.Match(line);
if (match.Success)
{
localId = long.Parse(match.Groups[1].Value);
var isSubAsset = localId != mainAssetLocalId;
delete = (isSubAsset && (index < 0 || indices.Contains(subAssetIndex)) == keepMain) || (!isSubAsset && !keepMain);
if (isSubAsset) subAssetIndex++;
}
if (!delete)
{
if (subAssetIndex >= yaml.Count) yaml.Add((localId, new StringBuilder()));
yaml[subAssetIndex].Item2.Append(line + System.Environment.NewLine);
}
}
}
return yaml.Select(x => (x.Item1, x.Item2.ToString())).ToArray();
}
public static Object GetSubAsset(Object subAsset, Object mainAsset)
{
try
{
return AssetDatabase.IsSubAsset(subAsset) ? subAsset : null;
}
catch (System.Exception)
{
var allSubAssets = GetNonSceneSubAssets(mainAsset);
var subIndex = allSubAssets
.Select((subAsset, index) => (subAsset, index))
.First(x => x.subAsset == subAsset)
.index;
return GetSubAssetsAtIndicesInternal(mainAsset, allSubAssets, subIndex).FirstOrDefault();
}
}
public static Object[] GetNonSceneSubAssets(Object mainAsset)
{
var assetPath = AssetDatabase.GetAssetPath(mainAsset);
return assetPath.Contains(".unity")
? new Object[] { }
: AssetDatabase.LoadAllAssetsAtPath(assetPath).Where(x => x != mainAsset).ToArray();
}
public static int GetSubAssetCount(string path)
{
return GetSubAssetCount(AssetDatabase.LoadMainAssetAtPath(path));
}
public static int GetSubAssetCount(Object mainAsset)
{
return GetNonSceneSubAssets(mainAsset).Length;
}
public static Object[] GetAllSubAssets(Object mainAsset)
{
return GetSubAssetsAtIndices(mainAsset, new int[] { -1 });
}
public static Object GetSubAssetAtIndex(Object mainAsset, int index)
{
return GetSubAssetsAtIndices(mainAsset, new int[] { index }).FirstOrDefault();
}
public static Object[] GetSubAssetsAtIndices(Object mainAsset, params int[] indices)
{
return GetSubAssetsAtIndicesInternal(mainAsset, null, indices);
}
private static Object[] GetSubAssetsAtIndicesInternal(Object mainAsset, Object[] allSubAssets = null, params int[] indices)
{
var yamlIdentifiers = ParseSubAssetsAtIndex(mainAsset, indices);
var yamlAssets = yamlIdentifiers.Skip(1).ToArray();
if (allSubAssets == null) allSubAssets = GetNonSceneSubAssets(mainAsset);
var subAssets = new List<Object>();
var subIndex = 0;
foreach (var yamlAsset in yamlAssets)
{
try
{
var subAsset = allSubAssets[subIndex];
if (AssetDatabase.IsSubAsset(subAsset)) subAssets.Add(subAsset);
}
catch (System.Exception)
{
var subAsset = UnknownObject.CreateInstance(yamlAsset.Item1, yamlAsset.Item2, allSubAssets[subIndex], subIndex, (subIndex) =>
{
RemoveSubAssetAtIndex(mainAsset, subIndex);
});
subAssets.Add(subAsset);
}
subIndex++;
}
return subAssets.ToArray();
}
public static void RemoveAllSubAssets(Object mainAsset)
{
RemoveSubAssetAtIndex(mainAsset, -1);
}
public static void RemoveSubAssetAtIndex(Object mainAsset, int index)
{
RemoveSubAssetsAtIndices(mainAsset, new int[] { index });
}
public static void RemoveSubAssetsAtIndices(Object mainAsset, int[] indices)
{
EditorApplication.delayCall += () =>
{
var ownerPath = AssetDatabase.GetAssetPath(mainAsset);
string tempFile = Path.GetTempFileName();
using (var sw = new StreamWriter(tempFile))
{
var yaml = string.Join("", ParseSubAssetsAtIndex(mainAsset, indices, true).Select(x => x.Item2).ToArray());
sw.Write(yaml);
}
File.Delete(ownerPath);
File.Move(tempFile, ownerPath);
AssetDatabase.ImportAsset(ownerPath);
};
}
}
It provides some helpful API, such as suggested by @slippyfrog (above):
AssetDatabaseHelper.GetSubAssetCount()
AssetDatabaseHelper.GetSubAssetAtIndex(o, i)
AssetDatabaseHelper.RemoveSubAssetAtIndex(o, i)
Additionally it has a few more features for working with what I call “UnknownObjects”.
I hope this helps whoever is still searching the internet for this answer! 