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 serialised to a DataContainer and deserialised by a DataBuilder.
After this initial conversion from the specialised 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.
Using the DataTranslators#CONFIGURATION_NODE implementation of DataTranslator, we 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.persistence.DataTranslator;
import org.spongepowered.api.data.persistence.DataTranslators;
public ConfigurationNode translateToConfig(HealthData data) {
final DataTranslator<ConfigurationNode> translator = DataTranslators.CONFIGURATION_NODE;
final DataView container = data.toContainer();
return translator.translate(container);
}
Code Example: Deserializing a HealthData instance from Configurate
import java.util.Optional;
public Optional<HealthData> translateFromConfig(ConfigurationNode node) {
final DataTranslator<ConfigurationNode> translator = DataTranslators.CONFIGURATION_NODE;
final DataView container = translator.translate(node);
return deserializeHealth(container);
}
DataFormat¶
An alternative to using a DataTranslator
is to use DataFormat, which allows you to store a
DataContainer
in HOCON, JSON or NBT format. You can also recreate DataContainers using DataFormats
. Sponge
provided DataFormat
implementations are available in the DataFormats class.
For example, we can use the DataFormats#JSON DataFormat
which allows us to create a JSON representation
of a DataContainer
. The output JSON could then easily be stored in a database. We can then use the same
DataFormat
to recreate the original DataContainer
from this JSON when required.
Imports for code examples
import org.spongepowered.api.Sponge;
import org.spongepowered.api.data.DataContainer;
import org.spongepowered.api.data.persistence.DataFormats;
import org.spongepowered.api.item.inventory.ItemStackSnapshot;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Optional;
Code Example: Serializing an ItemStackSnapshot to JSON format
String json = DataFormats.JSON.write(itemStack.toContainer());
Code Example: Deserializing an ItemStackSnapshot from JSON format
DataContainer container = DataFormats.JSON.read(json);
Code Example: Writing an ItemStackSnapshot to a file using NBT
public void writeItemStackSnapshotToFile(ItemStackSnapshot itemStackSnapshot, Path path) {
DataContainer dataContainer = itemStackSnapshot.toContainer();
try (OutputStream outputStream = Files.newOutputStream(path)) {
DataFormats.NBT.writeTo(outputStream, dataContainer);
} catch (IOException e) {
// For the purposes of this example, we just print the error to the console. However,
// as this exception indicates the file didn't save, you should handle this in a way
// more suitable for your plugin.
e.printStackTrace();
}
}
Code Example: Reading an ItemStackSnapshot from a file using NBT
public Optional<ItemStackSnapshot> readItemStackSnapshotFromFile(Path path) {
try (InputStream inputStream = Files.newInputStream(path)) {
DataContainer dataContainer = DataFormats.NBT.readFrom(inputStream);
return Sponge.getDataManager().deserialize(ItemStackSnapshot.class, dataContainer);
} catch (IOException e) {
e.printStackTrace();
}
return Optional.empty();
}