Can I access and *change* BP arrays and structs from code?

I’ve got a rather esoteric problem.

I need to access a blueprint’s array of editor-defined structs and add a new one to them in an editor plugin.
Hell, in a pinch, I could reduce it to accessing and modifying a blueprint’s array of FStrings or something.

Is there any way to do this?

I’ve looked and there’s no problem reading arrays of basic engine datatypes, but setting seems to be a whole other kettle of fish.

(No, I can’t really derive the BP from C++ code, since this would mean my users would have to add random bits of my C++ code to their own project.)

I’ve also posted this in the forum: See here for a small project that uses C++ to modify an array of Blueprint-defined structs stored inside a Blueprint. The code is not pretty but seems to work all right. I’d be happy to learn a better way of interacting with Blueprints from C++ but so far this is the best I could do. The main part:

void APropertyTestGameMode::AddEntry(AActor* Actor)
{
  if (Actor == nullptr)
  {
    return;
  }
  for (TFieldIterator<UProperty> PropIt(Actor->GetClass()); PropIt; ++PropIt)
  {
    // We are iterating over all properties of Actor
    UProperty* Property{ *PropIt };
    FString PropertyName{ GetNameSafe(Property) };
    if (PropertyName == "HammerOutputs")
    {
      // Hey, we found the HammerOutputs property. This should be an
      // array, right?
      UArrayProperty* ArrayProperty{ Cast<UArrayProperty>(Property) };
      if (ArrayProperty != nullptr)
      {
        // Yes it's an array. Now what can we do with an array
        // property?  Turns out, not much. We need some helper class
        // to make handling the array palatable.
        FScriptArrayHelper PropertyHelper{
          ArrayProperty, Property->ContainerPtrToValuePtr<void>(Actor) };

        // Using the helper it is not too difficult to add an
        // uninitialized member.
        int32 Index{ PropertyHelper.AddValue() };

        // But of course we want to add some data, not just default
        // initialized entries.  Therefore we need the Inner member
        // that describes how the entries of the array described by
        // ArrayProperty look like.
        UProperty* InnerProperty{ ArrayProperty->Inner };

        // Well, our array members should be HammerOutput structs,
        // right?  So InnerProperty should actually be a
        // UStructProperty, right? Right.
        UStructProperty* StructProperty{ Cast<UStructProperty>(InnerProperty) };
        
        if (StructProperty != nullptr)
        {
          // Now we have the UStruct property.  What can we do with
          // it?  If you paid attention to our array property you can
          // guess the answer.  Not much.
          
          // What we need is the UScriptStruct (which derives from
          // UStruct) that is stored inside the StructProperty.
          UScriptStruct* Struct{ StructProperty->Struct };

          // By using the UStruct we can actually - you probably
          // guessed it, get UProperty objects for our properties.
          // Let's do this!
          
          // Well, wait a moment. We would like to use
          // Struct->FindPropertyByName here, but that does not work
          // so well.  Or at all.  Because, the name of the property
          // MyFloat is not MyFloat but actually something like
          // MyFloat_6_34283427842342987.  The thing we want to check
          // is the FriendlyName.  Or something.  Perhaps there is a
          // way to find the property using the friendly name, but I
          // don't know what it is. So we have to do this the hard way
          // by iterating over all properties.  And while we're at it,
          // let's cast to UNumericProperty, or whatever type of
          // property is appropriate for each variable, so that we
          // can, you know, actually do something with the result.
                                        
          UNumericProperty* MyFloatProperty{ nullptr };
          UBoolProperty* MyBoolProperty{ nullptr };
          UStrProperty* MyStringProperty{ nullptr };
          UArrayProperty* MyInnerArrayProperty{ nullptr };

          for (UProperty* Prop = Struct->PropertyLink;
               Prop != nullptr;
               Prop = Prop->PropertyLinkNext)
          {
            if (Prop->GetName().StartsWith("MyFloat_"))
            {
              MyFloatProperty = Cast<UNumericProperty>(Prop);
            }
            else if (Prop->GetName().StartsWith("MyBool_"))
            {
              MyBoolProperty = Cast<UBoolProperty>(Prop);
            }
            else if (Prop->GetName().StartsWith("MyString_"))
            {
              MyStringProperty = Cast<UStrProperty>(Prop);
            }
            else if (Prop->GetName().StartsWith("MyStringArray_"))
            {
              MyInnerArrayProperty = Cast<UArrayProperty>(Prop);
            }
            else
            {
              UE_LOG(LogTemp, Log,
                     TEXT("Unknown: %s"), *Prop->GetName());
            }
          }
                                        
          // So, we now have some other properties. Can we do
          // something with them?  Well, not exactly, not right now.
          // We first need to figure out the address of the value to
          // set.  Remember the PropertyHelper above, and the Index we
          // got from the AddValue() operation?  Of course you do!
          uint8* MyStructPointer{ PropertyHelper.GetRawPtr(Index) };

          if (MyFloatProperty != nullptr)
          {
            // Yay! We have another property that allows us to finally
            // set one of the values.
            MyFloatProperty->SetFloatingPointPropertyValue(
              MyStructPointer, Index * 1.0f);

            // Easy, right?
          }
          if (MyBoolProperty != nullptr)
          {
            // Same old, same old, only for boolean values.
            MyBoolProperty->SetPropertyValue_InContainer(
              MyStructPointer, Index%2 == 0, 0);
          }
          if (MyStringProperty != nullptr)
          {
            // We're really getting the hang of it.
            MyStringProperty->SetPropertyValue_InContainer(
              MyStructPointer,
              FString::Printf(TEXT("Howdy %d!"), Index), 0);
          }
          if (MyInnerArrayProperty != nullptr)
          {
            // Uh oh. Another array. Those weeping noises you hear are
            // surely a figment of your imagination.  Can't have
            // anything to do with me breaking down and, no, no no
            // siree, no way. We *sniff* get another helper and...
            FScriptArrayHelper InnerHelper{ MyInnerArrayProperty,
                MyInnerArrayProperty->ContainerPtrToValuePtr<void>(
                  MyStructPointer) };
            
            // ... you were saying? I'm sorry, I got distracted for a
            // moment. It's just, you know, life is really tough
            // sometimes...
            UStrProperty* InnerStringProperty{
              Cast<UStrProperty>(MyInnerArrayProperty->Inner) };

            if (InnerStringProperty != nullptr)
            {
              // Let's add a variable number of strings so that we
              // *sigh*, you know... see and stuff...
              for (int32 I = 0; I < Index; ++I)
              {
                // Value, pointer, you know the drill...
                int32 J{ InnerHelper.AddValue() };
                ensure(J == I);
		
                uint8* MyInnerStringPointer{
                  InnerHelper.GetRawPtr(J) };
                
                InnerStringProperty->SetPropertyValue_InContainer(
                  MyInnerStringPointer,
                  FString::Printf(TEXT(">>> %d"), J), 0);
              }
            }
          }
        }
      }
    }
  }
}

One thing I ran into is that it crashed with a UNumericProperty when I clicked on the inspector to check the data, but when I switched to UFloatProperty that also used SetPropertyValue_InContainer instead of SetFloatingPointPropertyValue, that seems to have gone away.

A very comprehensive answer on how to use the reflection system. Kudos!