Unity no longer seems to update indirect Component references upon Instantiation of a runtime object

I previously created a MonoBehaviour which could use code-defined components as templates for GameObjects created at design time to in effect create Prefabs at runtime and use them in a kind of UI list, which worked something like this:

class ListUI : MonoBehaviour {
    // templates
    TextData m_TextTemplate;
    ...

    // core classes
    class ItemComponent : Component {
        public ItemData data;
    }
    [Serializable]
    class ItemData {
        public Text Label;
    }

    // derived ItemData classes
    [Serializable]
    class TextData : ItemData {
        public Text Description;
    }
    ...

    public void UpdateContent(IEnumerable<KeyValuePair<string, object>> content) {
        // find a template corresponding to the value's type, Instantiate() the template, and modify the clone
    }
}

The idea was that at runtime, in Awake(), templates would have an ItemComponent added to them and they would be hidden. When UpdateContent was run, templates would be Instantiated() and have their references within the ItemComponent updated automatically as a result of the cloning process, as if to use the templates as Prefabs created at runtime.

This used to work, but something happened and now the references in the cloned object’s ItemComponent don’t get updated - in fact, the cloned object’s ItemComponent.data is simply set to null. Before, the cloned object’s ItemComponent.data would contain references updated to the clone’s own components. I reverted to an earlier iteration of the class and it still didn’t work, so I don’t think it was a change I made. I’ve also already tested the templates - at runtime, the templates’ ItemComponents contain the correct references.

I’m wondering if there’s a better way to do what I’m trying to achieve or if there’s a way to make this behavior work again. Either response would be appreciated.

I hacked the problem away by emulating the previous behavior with reflection:

private void PiecewiseCopyHack(ItemComponent from, ItemComponent to)
{
    Type t = from.data.GetType();
    // for now, I only need to copy fields - if someone uses this code you probably want to copy other stuff too
    var fields = t.GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);

    List<string> path = new List<string>();

    to.data = (ItemData)Activator.CreateInstance(t);
    foreach (var field in fields) {
        var fieldVal = field.GetValue(from.data);

        if (fieldVal is Component comp) {
            // starting at the comp itself, add every GameObject name upwards in the GO tree up to and including the ItemComponent's transform.
            Transform tr = comp.transform;
            while (tr != null && tr != from.transform) {
                path.Add(tr.name);
                tr = tr.parent;
            }

            Transform toTransform = to.transform;
            // path is down to up
            for (int i = path.Count - 1; i >= 0; i--) {
                // this is a utility function in my library which simply gets a direct child with the given name
                toTransform = toTransform.GetChildNamed(path[i]);
            }

            // get component at same relative path to ItemComponent
            field.SetValue(to.data, toTransform.GetComponent(field.FieldType));
            path.Clear();
        }
        else {
            // simply soft copy field if it isn't a component
            field.SetValue(to.data, fieldVal);
        }
    }
}