Search code examples
kotlinjetbrains-ideintellij-plugin

IntelliJ does not serialize nested objects' properties of PersistentStateComponent subclass


This is how my custom PersistentStateComponent subclass looks like:

@State(name = "Configurations", storages = [Storage("conf.xml")])
@Service(Service.Level.PROJECT)
class CustomConfigurationService : PersistentStateComponent<CustomConfigurationService> {

    var configurations = Configurations()

    override fun getState() = this

    override fun loadState(service: CustomConfigurationService) {
        XmlSerializerUtil.copyBean(service, this)
    }

    // ...
}
data class Configurations(
    val aBoolean: Boolean = false,
    val aString: String? = null,
    val anotherString: String? = null
)

This is how the configurations are stored (after being changed via the UI):

<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
  <component name="Configurations">
    <option name="configurations">
      <Configurations />
    </option>
  </component>
</project>

If I change the super type to PersistentStateComponent<Configurations>, the file is not created at all.

Why doesn't IntelliJ serialize the Configurations object's properties as well? What can I do, other than placing Configurations's properties directly inside CustomConfigurationService?


Solution

  • It turns out that I was terribly misguided. Here's how the final code looks like:

    import com.intellij.openapi.components.BaseState
    
    class Configurations : BaseState() {
        // property() and string() are BaseState methods
        var aBoolean by property(false)
        var aString by string(null)
        var anotherString by string(null)
    }
    
    import com.intellij.openapi.components.SimplePersistentStateComponent
    import com.intellij.util.xmlb.XmlSerializerUtil
    
    @State(name = "Configurations", storages = [Storage("conf.xml")])
    @Service(Service.Level.PROJECT)
    class CustomConfigurationService :
        // This takes care of everything else and gives us a .state property
        SimplePersistentStateComponent<Configurations>(Configurations()) {
    
        // ...which I shadow using this other property.
        override var configurations: Configurations
            get() = state
            set(value) = XmlSerializerUtil.copyBean(value, state)
        
        companion object {
            fun getInstance() = service<ConfigurationService>()
        }
    
    }
    

    conf.xml's contents eventually looks like this:

    <?xml version="1.0" encoding="UTF-8"?>
    <project version="4">
      <component name="Configurations">
        <option name="aBoolean" value="true" />
        <option name="aString" value="Foobar">
      </component>
    </project>