Line 5: |
Line 5: |
| == Serialization and Deserialization == | | == Serialization and Deserialization == |
| | | |
− | The primary use for Codecs is to serialize java objects to some serialized type, such as a JsonElement or an INBT, and to deserialize an serialized object back to its proper java type. This is accomplished with <code>Codec#encodeStart</code> and <code>Codec#parse</code>, respectively. Given a Codec<SomeJavaType> and a DynamicOps<SomeSerializedType>, we can convert instances of SomeJavaType to instances of SomeSerializedType and back. | + | The primary use for Codecs is to serialize java objects to some serialized type, such as a JsonElement or a Tag, and to deserialize a serialized object back to its proper java type. This is accomplished with <code>Codec#encodeStart</code> and <code>Codec#parse</code>, respectively. Given a Codec<SomeJavaType> and a DynamicOps<SomeSerializedType>, we can convert instances of SomeJavaType to instances of SomeSerializedType and back. |
| | | |
| Each of these methods take a [[DynamicOps]] instance and an instance of the object we are serializing or deserializing, and returns a DataResult: | | Each of these methods take a [[DynamicOps]] instance and an instance of the object we are serializing or deserializing, and returns a DataResult: |
Line 12: |
Line 12: |
| // let someCodec be a Codec<SomeJavaType> | | // let someCodec be a Codec<SomeJavaType> |
| // let someJavaObject be an instance of SomeJavaType | | // let someJavaObject be an instance of SomeJavaType |
− | // let someNBT and someJsonElement be instances of INBT and JsonElement, respectively | + | // let someTag and someJsonElement be instances of Tag and JsonElement, respectively |
| | | |
− | // serialize some java object to INBT | + | // serialize some java object to Tag |
− | DataResult<INBT> result = someCodec.encodeStart(NBTDynamicOps.INSTANCE, someJavaObject); | + | DataResult<Tag> result = someCodec.encodeStart(NBTOps.INSTANCE, someJavaObject); |
| | | |
− | // deserialize some INBT instance back to a proper java object | + | // deserialize some Tag instance back to a proper java object |
− | DataResult<SomeJavaType> result = someCodec.parse(NBTDynamicOps.INSTANCE, someNBT); | + | DataResult<SomeJavaType> result = someCodec.parse(NBTOps.INSTANCE, someTag); |
| | | |
| // serialize some java object to a JsonElement | | // serialize some java object to a JsonElement |
Line 50: |
Line 50: |
| Each vanilla <code>Registry</code> acts as the Codec for the type of object the registry contains; e.g. <code>Registry.BLOCK</code> is itself a <code>Codec<Block></code>. Forge Registries, however, do not currently implement Codec and cannot yet be used in this way; custom codecs must be created for forge-specific registries that are not tied to specific vanilla registries. | | Each vanilla <code>Registry</code> acts as the Codec for the type of object the registry contains; e.g. <code>Registry.BLOCK</code> is itself a <code>Codec<Block></code>. Forge Registries, however, do not currently implement Codec and cannot yet be used in this way; custom codecs must be created for forge-specific registries that are not tied to specific vanilla registries. |
| | | |
− | Of particular note here is the CompoundNBT.CODEC, which can be used to e.g. serialize a CompoundNBT into a json file. This has a notable limitation in that CompoundNBT.CODEC *cannot* safely deserialize lists of numbers from json, due to the strong typing of ListNBT and the way that the NBTDynamicOps deserializer reads numeric values. | + | Of particular note here is the CompoundTag.CODEC, which can be used to e.g. serialize a CompoundTag into a json file. This has a notable limitation in that CompoundTag.CODEC *cannot* safely deserialize lists of numbers from json, due to the strong typing of ListTag and the way that the NBTOps deserializer reads numeric values. |
| | | |
| = Creating Codecs = | | = Creating Codecs = |
Line 65: |
Line 65: |
| | | |
| public int getSomeInt() { return this.someInt; } | | public int getSomeInt() { return this.someInt; } |
− | public Item getItem() { return this.someItem; } | + | public Item getItem() { return this.item; } |
| public List<BlockPos> getBlockPositions() { return this.blockPositions; } | | public List<BlockPos> getBlockPositions() { return this.blockPositions; } |
| } | | } |
Line 88: |
Line 88: |
| * a <code>Codec<Item></code> | | * a <code>Codec<Item></code> |
| * a <code>Codec<List<BlockPos>></code> | | * a <code>Codec<List<BlockPos>></code> |
− | And then we'll need to assemble these into a <code>Codec<ExampleCodecClass</code>. | + | And then we'll need to assemble these into a <code>Codec<ExampleCodecClass></code>. |
| | | |
| As previously mentioned, we can use <code>Codec.INT</code> for the integer codec, and <code>Registry.ITEM</code> for the Item codec. We don't have a builtin codec for list-of-blockpos, but we can use BlockPos.CODEC to create one. | | As previously mentioned, we can use <code>Codec.INT</code> for the integer codec, and <code>Registry.ITEM</code> for the Item codec. We don't have a builtin codec for list-of-blockpos, but we can use BlockPos.CODEC to create one. |
| | | |
− | == Lists == | + | ==Lists== |
| The <code>Codec#listOf</code> instance method can be used to generate a codec for a List from an existing codec: | | The <code>Codec#listOf</code> instance method can be used to generate a codec for a List from an existing codec: |
| | | |
| <syntaxhighlight lang="java"> | | <syntaxhighlight lang="java"> |
| // BlockPos.CODEC is a Codec<BlockPos> | | // BlockPos.CODEC is a Codec<BlockPos> |
− | Codec<List<BlockPos>> = BlockPos.CODEC.listOf(); | + | Codec<List<BlockPos>> blockPosListCodec = BlockPos.CODEC.listOf(); |
| </syntaxhighlight> | | </syntaxhighlight> |
| | | |
− | Codecs created via listOf() serialize things to listlike objects, such as [] json arrays or ListNBTs. | + | Codecs created via listOf() serialize things to listlike objects, such as [] json arrays or ListTags. |
| + | |
| + | Deserializing a list in this manner produces an ''immutable'' list. If a mutable list is needed, [[Codecs#Equivalent_Types_and_xmap|xmap]] can be used to convert the list after deserializing. |
| | | |
| == Records == | | == Records == |
− | RecordCodecBuilder is used to generate codecs that serialize instances of classes with explicitly named fields, like our example above. Codecs created via RecordCodecBuilder serialize things to maplike objects, such as {} json objects or CompoundNBTs. | + | RecordCodecBuilder is used to generate codecs that serialize instances of classes with explicitly named fields, like our example above. Codecs created via RecordCodecBuilder serialize things to maplike objects, such as {} json objects or CompoundTags. |
| | | |
| RecordCodecBuilder can be used in several ways, but the simplest form is as follows: | | RecordCodecBuilder can be used in several ways, but the simplest form is as follows: |
Line 128: |
Line 130: |
| | | |
| ===Optional and Default Values in Record Fields=== | | ===Optional and Default Values in Record Fields=== |
− | When RecordCodecBuilder is used as shown above, all of the fields are *required* to be in the serialized object (the JsonObject/CompoundNBT/etc), or the entire thing will fail to parse when the codec tries to deserialize it. If we wish to have optional or default values, we have several alternatives of fieldOf() we can use. | + | When RecordCodecBuilder is used as shown above, all of the fields are *required* to be in the serialized object (the JsonObject/CompoundTag/etc), or the entire thing will fail to parse when the codec tries to deserialize it. If we wish to have optional or default values, we have several alternatives of fieldOf() we can use. |
| | | |
| * <code>someCodec.optionalFieldOf("field_name")</code> creates a field for an Optional. If the field in the json/nbt is not present or invalid, it will deserialize as an empty optional. Empty optionals will not be serialized; the field will be omitted from the json or nbt. | | * <code>someCodec.optionalFieldOf("field_name")</code> creates a field for an Optional. If the field in the json/nbt is not present or invalid, it will deserialize as an empty optional. Empty optionals will not be serialized; the field will be omitted from the json or nbt. |
Line 134: |
Line 136: |
| | | |
| When using optional fields, be wary that if the field contains bad data or otherwise fails to serialize, the error will be silently caught, and the field will serialize as the default value instead! | | When using optional fields, be wary that if the field contains bad data or otherwise fails to serialize, the error will be silently caught, and the field will serialize as the default value instead! |
| + | |
| + | ===Boxing values as objects=== |
| + | In some situations, we may need to serialize a single value as a single-field object. We can use fieldOf to box a single value in this way without needing the entire RecordCodecBuilder process: |
| + | |
| + | <syntaxhighlight lang="java"> |
| + | public static final Codec<Integer> BOXED_INT_CODEC = Codec.INT.fieldOf("value").codec(); |
| + | |
| + | JsonElement value = BOXED_INT_CODEC.encodeStart(JsonOps.INSTANCE, 5).result().get(); |
| + | </syntaxhighlight> |
| + | |
| + | Which serializes the following output: |
| + | |
| + | <syntaxhighlight lang="json"> |
| + | {"value":5} |
| + | </syntaxhighlight> |
| | | |
| ==Unit== | | ==Unit== |
Line 141: |
Line 158: |
| The <code>Codec.pair(codecA, codecB)</code> static method takes two codecs and generates a Codec<Pair<A,B>> from them. | | The <code>Codec.pair(codecA, codecB)</code> static method takes two codecs and generates a Codec<Pair<A,B>> from them. |
| | | |
− | Using this function requires that codecA be a codec that serializes to a string.
| + | The only valid arguments for this method are codecs that serialize to objects with explicit fields, such as codecs created using [[Codecs#Records|RecordCodecBuilder]] or [[Codecs#Boxing_values_as_objects|fieldOf]]. Codecs that serialize nothing (such as [[Codecs#Unit|unit codecs]]) are also valid as they act as objects-with-no-fields. |
| + | |
| + | The resulting Pair codec will serialize a single object that has all of the fields of the two original codecs. For example: |
| + | <syntaxhighlight lang="java"> |
| + | public static final Codec<Pair<Integer,String>> PAIR_CODEC = Codec.pair( |
| + | Codec.INT.fieldOf("value").codec(), |
| + | Codec.STRING.fieldOf("name").codec()); |
| + | |
| + | JsonElement encodedPair = PAIR_CODEC.encodeStart(JsonOps.INSTANCE, Pair.of(5, "cheese").result().get(); |
| + | </syntaxhighlight> |
| + | This codec serializes the above value to: |
| + | <syntaxhighlight lang="json"> |
| + | { |
| + | "value": 5, |
| + | "name": "cheese" |
| + | } |
| + | </syntaxhighlight> |
| + | |
| + | Codecs that serialize to objects with undefined fields such as [[Codecs#Maps|unboundedMap]] may cause strange and unpredictable behaviour when used here; these objects should be boxed via fieldOf when used in a pair codec. |
| | | |
| ==Either== | | ==Either== |
Line 160: |
Line 195: |
| </syntaxhighlight> | | </syntaxhighlight> |
| | | |
− | The serialized form of maps serialized by this codec will be a JsonObject or CompoundNBT, whose fields are the key-value pairs in the map. | + | The serialized form of maps serialized by this codec will be a JsonObject or CompoundTag, whose fields are the key-value pairs in the map; the map's keys will be used as the field names, and the map's values will be the values of those fields |
| | | |
− | A limitation of using unboundedMap is that it only supports key codecs that serialize to Strings (including codecs for things like ResourceLocation that aren't Strings themselves but still serialize to strings). To create a codec for a Map whose keys are not fundamentally strings, the Map must be serialized as a list of key-value entries instead of using unboundedMap. As Codec.pair() also requires that the first codec serialize to a string, the pair function cannot be used for this purpose either. | + | A limitation of using unboundedMap is that it only supports key codecs that serialize to Strings (including codecs for things like ResourceLocation that aren't Strings themselves but still serialize to strings). To create a codec for a Map whose keys are not fundamentally strings, the Map must be serialized as a list of key-value pairs instead of using unboundedMap. |
| | | |
| ==Equivalent Types and xmap== | | ==Equivalent Types and xmap== |
Line 229: |
Line 264: |
| | | |
| Several examples of vanilla classes that use dispatch codecs: | | Several examples of vanilla classes that use dispatch codecs: |
− | * RuleTest and IRuleTestType | + | * RuleTest and RuleTestType |
− | * IParticleData and ParticleType | + | * BlockPlacer and BlockPlacerType |
− | * ConfiguredPlacement and Placement | + | * ConfiguredDecorator and FeatureDecorator |
| + | |
| + | === Registering MapCodecCodecs for Dispatch Subcodecs === |
| + | |
| + | When registering a subcodec to any dispatch codec registry, the registered subcodec should be an instance of MapCodecCodec, or the subcodec will be nested in its own object when serialized. |
| + | |
| + | For example, suppose we register a single-field subcodec, where we use fieldOf-and-xmap to convert an int to our int-holding Thing: |
| + | |
| + | <syntaxhighlight lang="java"> |
| + | public record Thing(int n){} |
| + | public static Codec<Thing> CODEC = Codec.INT.fieldOf("n").codec().xmap(Thing::new, Thing::n); |
| + | </syntaxhighlight> |
| + | |
| + | This results in this json when serialized: |
| + | |
| + | <syntaxhighlight lang="json"> |
| + | "some_thing": |
| + | { |
| + | "type": "ourmod:thing", |
| + | "value": { |
| + | "n": 5 |
| + | } |
| + | } |
| + | </syntaxhighlight> |
| + | |
| + | This occurs because xmap does not produce a MapCodecCodec, and if this nested object is not desired, then our registered subcodec must be a MapCodecCodec. |
| + | |
| + | However, fieldOf() produces a MapCodec, which has a codec() method, which does produce a MapCodecCodec. We can rearrange our codec builder, which then produces a cleaner json: |
| + | |
| + | <syntaxhighlight lang="java"> |
| + | public static Codec<Thing> CODEC = Codec.INT // Primitive codec |
| + | .fieldOf("n") // MapCodec |
| + | .xmap(Thing::new, Thing::n) // MapCodec |
| + | .codec(); // MapCodecCodec! That's what we want. |
| + | </syntaxhighlight> |
| + | |
| + | <syntaxhighlight lang="json"> |
| + | "some_thing": |
| + | { |
| + | "type": "ourmod:thing", |
| + | "n": 5 |
| + | } |
| + | </syntaxhighlight> |
| + | |
| + | RecordCodecBuilder also produces MapCodecCodecs. |
| | | |
| =External Links= | | =External Links= |
| * [https://github.com/Mojang/DataFixerUpper/blob/master/src/main/java/com/mojang/serialization/Codec.java Codecs in Mojang's official public DataFixerUpper repository] | | * [https://github.com/Mojang/DataFixerUpper/blob/master/src/main/java/com/mojang/serialization/Codec.java Codecs in Mojang's official public DataFixerUpper repository] |
| * [https://kvverti.github.io/Documented-DataFixerUpper/snapshot/com/mojang/serialization/Codec.html#flatXmap-java.util.function.Function-java.util.function.Function- Unofficial Codec Javadocs] | | * [https://kvverti.github.io/Documented-DataFixerUpper/snapshot/com/mojang/serialization/Codec.html#flatXmap-java.util.function.Function-java.util.function.Function- Unofficial Codec Javadocs] |