Serializing Data¶
While an ImmutableDataManipulator
is a good way to store data while the server is running, it will not persist over
a restart. However, every DataManipulator
implements the DataSerializable
interface and thus can be serialized
to a DataContainer
and deserialized by a DataBuilder
.
After this initial conversion from the specialized DataManipulator
to a more general structure, the DataContainer
can be further processed.
DataContainer and DataView¶
A DataView
is a general-purpose structure for holding any kind of data. It supports multiple values and even nested
DataView
s as a value, thus allowing for a tree-like structure. Every value is identified by a DataQuery
.
A DataContainer
is a root DataView
.
Every DataSerializable
provides a toContainer()
method which will create and return a DataContainer
.
As an example, calling toContainer()
on a HealthData
instance will yield a DataContainer
containing two
values, one for the current and one for the maximum health, each identified by the DataQuery
of the respective
Key
.
import org.spongepowered.api.data.DataContainer;
import org.spongepowered.api.data.key.Keys;
DataContainer serializedHealth = healthData.toContainer();
double currentHealth = serializedHealth.getDouble(Keys.HEALTH.getQuery()).get();
currentHealth == healthData.health().get(); // true
Converting this container back into a HealthData
instance is done by the corresponding DataBuilder
. Those are
registered and managed by the DataManager
. It can either be obtained from a valid Game
instance or using the
Sponge
utility class. The DataManager
provides a method to get the appropriate
DataBuilder
to deserialize a given class and additionally a shorthand method to get the DataBuilder
and have it
do the deserialization in one step. Both of the following code examples are functionally equivalent.
Code Example: Deserialization, the long way
import org.spongepowered.api.data.DataView;
import org.spongepowered.api.data.manipulator.mutable.entity.HealthData;
import org.spongepowered.api.util.persistence.DataBuilder;
import java.util.Optional;
public Optional<HealthData> deserializeHealth(DataView container) {
final Optional<DataBuilder<HealthData>> builder = Sponge.getDataManager().getBuilder(HealthData.class);
if (builder.isPresent()) {
return builder.get().build(container);
}
return Optional.empty();
}
Code Example: Deserialization, the short way
import org.spongepowered.api.data.manipulator.mutable.entity.HealthData;
public Optional<HealthData> deserializeHealth(DataView container) {
return Sponge.getDataManager().deserialize(HealthData.class, container);
}
The deserializeHealth
function will return Optional.empty()
if there is no DataBuilder
registered for
HealthData
or the supplied DataContainer
is empty. If invalid data is present in the DataContainer
, an
InvalidDataException
will be thrown.
DataTranslator¶
In Sponge, generally the implementations MemoryDataView
and MemoryDataContainer
are used, which reside in
memory only and thus will not persist over a server restart. In order to persistently store a DataContainer
, it
first has to be converted into a storable representation. This can be done by using an implementation of the
DataTranslator
interface, for example the ConfigurateTranslator
, which can convert a DataView
to a
ConfigurationNode
and vice versa. ConfigurationNode
s can then be written to and read from persistent files
using the Configurate Library.
Code Example: Serializing a HealthData instance to Configurate
import ninja.leaping.configurate.ConfigurationNode;
import org.spongepowered.api.data.translator.ConfigurateTranslator;
public void writeToConfig(HealthData data, ConfigurationNode config) {
final ConfigurateTranslator translator = ConfigurateTranslator.instance();
final DataView container = data.toContainer();
translator.translateContainerToData(config, container);
}
Code Example: Deserializing a HealthData instance from Configurate
public Optional<HealthData> readHealthFromConfig(ConfigurationNode config) {
final ConfigurateTranslator translator = ConfigurateTranslator.instance();
final DataView container = translator.translateFrom(config);
return deserializeHealth(container);
}