Eu vim aqui procurando as mesmas respostas. Acontece que o problema é que o JPA não sabe que seu objeto está sujo. Isso foi resolvido implementando métodos equals()/hashcode() nesses objetos complexos. No seu exemplo, implemente equals e hashcode para ProjectProperty
Feito isso, o JPA é capaz de identificar por meio desses métodos que o objeto subjacente está sujo e converte e persiste.