Codecs are an abstraction layer around [[DynamicOps|DynamicOps]] which allow objects to be serialized and deserialized in different contexts such as JSON or NBT. It creates an easy way to read and interpret objects without the need of manual labor.
==Codec Serialization and Deserialization==
Codec Serialization and Deserialization is handled through two main methods: <code>Codec#encodeStart</code> and <code>Codec#parse</code> respectively. Each of these methods returns a <code>DataResult</code> which holds the encoded object type or the decoded object class. An <code>Optional</code> of the resulting output can be grabbed via <code>DataResult#result</code>. If a custom message should be specified along with the error message, that can be specified using <code>DataResult#resultOrPartial</code>. Alternatively,<code>DataResult#getOrThrow</code>can be used which grabs the instance directly instead of an optional.
==Creating a Codec for a Class==
Let's assume there is the following class structure that a codec should be created for:
<syntaxhighlight lang="java">
public class ExampleCodecClass {
private final int field_1;
private final List<BlockPos> field_2;
private final Item field_3;
public ExampleCodecClass(int field_1, List<BlockPos> field_2, Item field_3) {...}
}
</syntaxhighlight>
For each basic object instance, a codec can be constructed using <code>RecordCodecBuilder::create</code>. This takes in a function that converts an <code>Instance</code> of an object, which is a group of codecs for each serializable field, to an <code>App</code>, which is an unary type constructor for allowing algorithms to be generalized using generics.
<syntaxhighlight lang="java">
public static final Codec<ExampleCodecClass> CODEC = RecordCodecBuilder.create(builder -> {
return ...;
});
</syntaxhighlight>
To add a list of valid codecs, which is denoted by <code>P</code>where n is the number of fields in the instance,<code>Instance#group</code>is used which takes in codecs converted into an <code>App</code> of some kind. This example will examine three such scenarios.
First, there is a primitive integer field. All primitive codecs are declared within the <code>Codec</code> class along with a few extra primitive streams (in this case we will use <code>Codec#INT</code>). To convert this codec into a valid key-pair form, the parameter name needs to be specified. This can be done using <code>Codec#fieldOf</code> which will take in a string which represents the key of this field. This will convert the codec into a MapCodec which as the name states creates a key-value pair to deserialize the instance from. From there, how to serialize the instance from the class object must also be specified. This can be done using <code>MapCodec#forGetter</code> which takes in a function that converts the class object to the type instance, hence the getter method name. This creates a <code>RecordCodecBuilder</code> which will be the final state of the codec as it is an instance of <code>App</code>.
<syntaxhighlight lang="java">
public static final Codec<ExampleCodecClass> CODEC = RecordCodecBuilder.create(builder -> {
return builder.group(Codec.INT.fieldOf("field_1").forGetter(obj -> obj.field_1),
...)...;
});
</syntaxhighlight>
Next, there is a list of <code>BlockPos</code> which has a premade codec within itself. However, a <code>Codec</code> needs to be converted into a <code>Codec</code>. Luckily, there are a few helpers within the codec class that allows some of these conversions to be trivial. In this case, <code>Codec#listOf</code> will convert a codec of some generic into a list of that generic. The process for attaching the codec is exactly the same.
A few other notable mentions that might be used within a codec:
{| class="wikitable" style="margin-left: auto; margin-right: auto; width: 1415px;" data-mce-style="margin-left: auto; margin-right: auto; width: 1415px;"
|-
| style="width: 52px;" data-mce-style="width: 52px;"|Method
| style="width: 640px;" data-mce-style="width: 640px;"|Description
|-
| style="width: 52px;" data-mce-style="width: 52px;"|intRange
| style="width: 640px;" data-mce-style="width: 640px;"|Creates a integer codec with a valid inclusive range.
|-
| style="width: 52px;" data-mce-style="width: 52px;"|floatRange
| style="width: 640px;" data-mce-style="width: 640px;"|Creates a float codec with a valid inclusive range.
|-
| style="width: 52px;" data-mce-style="width: 52px;"|doubleRange
| style="width: 640px;" data-mce-style="width: 640px;"|Creates a double codec with a valid inclusive range.
|-
| style="width: 52px;" data-mce-style="width: 52px;"|pair
| style="width: 640px;" data-mce-style="width: 640px;"|Create a pair using two codecs.
|-
| style="width: 52px;" data-mce-style="width: 52px;"|either
| style="width: 640px;" data-mce-style="width: 640px;"|Creates an either (an object with some fallback object) using two codecs.
|-
| style="width: 52px;" data-mce-style="width: 52px;"|unboundedMap
| style="width: 640px;" data-mce-style="width: 640px;"|Creates a map using two codecs.
|}
Last, there is a Item. This is optional and should default to <code>Items#AIR</code> when not defined. Here, there will be two techniques used to grab the associated codec. By default, a <code>Registry</code> is an instance of a codec. Therefore, the codec can be grabbed by specifying the registry instance (e.g. <code>Registry#ITEM</code>). What if there is no vanilla registry instance however? Then, another codec method can be used: <code>Codec#xmap</code>. This allows the associated object to be mapped to another object. A function specifies mapping the associated object to the new object for decoding and vice versa for encoding. For example, the <code>ResourceLocation</code> codec can be mapped to an <code>Item</code> through the forge registry instance.
To define a field as optional, <code>Codec#optionalFieldOf</code> should be used. One instance holds the value as an <code>Optional</code> while the other allows a defined default value.
Now, there is a product <code>P3</code> as there is three parameters. To convert this into an <code>App</code>, the method <code>P#apply</code> should be called. This takes in an <code>Applicative</code> which our builder is an instance of and a function that returns the class object given the specified wrapped arguments. Creating a new constructor is one way of creating the outputted codec for the class object.
<syntaxhighlight lang="java">
public static final Codec<ExampleCodecClass> CODEC = RecordCodecBuilder.create(builder -> {
return builder.group(Codec.INT.fieldOf("field_1").forGetter(obj -> obj.field_1),
BlockPos.CODEC.listOf().fieldOf("field_2").forGetter(obj -> obj.field_2),
ResourceLocation.CODEC.xmap(loc -> ForgeRegistries.ITEMS.getValue(loc), item -> item.getRegistryName()).optionalFieldOf("field_3", Items.AIR).forGetter(obj -> obj.field_3))
.apply(builder, ExampleCodecClass::new);
});
</syntaxhighlight>
==Limitations==
Codecs are required to abide by a String-Object key-pair. Any codec that does not have a String key will throw an error during encoding and decoding.
A group can have at most 16 inner codecs normally. This limitation is specified by the number of product generic classes available.