Posted December 19, 20222 yr Hello, I recently started implementation of Codec based serialization for my data objects and ran into some minor issues. I have some external utility class holding few parameters which is used by one data object (single parameter). The data object codecs are loaded via registry lookup and this extra parameter is mapped by extra key, which I would like to remove (even though I think that won't be possible as it could possibly cause issues when multiple objects are inlined, but in my case it's just one which would work). I have checked several documentations about codecs, but I have not found anything useful (is there any documentation by Mojang? I found only the DFU repository, which is for version 1.x, while MC is already on 5.x). I'll share code snippets which should explain the situation better than I do.. So I have some common class like this for example public class MyObj { public static final Codec<MyObj> CODEC = RecordCodecBuilder.create(i -> i.group( Codec.INT.fieldOf("i").forGetter(MyObj::i), Codec.INT.fieldOf("j").forGetter(MyObj::j) ).apply(i, MyObj::new)); } which I want to be loaded from format like this { "type": "namespace:id", "i": 1, "j": 2 } The actual codec inside the following class is mapped via Codec#dispatch method. My current implementation has it like this: public class DataObj { public static final Codec<DataObj> CODEC = MyObj.CODEC.fieldOf("key").xmap(DataObj::new, t -> t.myObj).codec(); private final MyObj myObj; private DataObj(MyObj myObj) { this.myObj = myObj; } } which expects to be in another Map object like this { "type": "namespace:id", "key": { "i": 1, "j": 2 } } Is there any way to inline it without the key parameter?
December 19, 20222 yr I don't really follow what you are trying to do from that description. 🙂 But if you want to change the codec based on a "type" field, look at Codec.partialDispatch() or one of its simplified helpers that call it. The basic idea is you tell it the name of type field, and then some functions that tell it how to map type -> Codec<value> and value -> type Here's a "simple" example I knocked up. I obviously don't recommend using the class name in a real world example as the type discriminator. You can use any "primitive" type as the typeCodec, e.g. a ResourceLocation public class TypedCodecRegistry<TYPE, VALUE> { // The codec for the type field private final Codec<TYPE> typeCodec; // The mapping of value -> type private final Function<VALUE, TYPE> typeFunction; // type -> real codec private final Map<TYPE, Codec<? extends VALUE>> CODECS = new HashMap<>(); public TypedCodecRegistry(Codec<TYPE> typeCodec, Function<VALUE, TYPE> typeFunction) { this.typeCodec = typeCodec; this.typeFunction = typeFunction; } public Codec<VALUE> codec() { return this.typeCodec.partialDispatch( "type", // The name of the type field // given a value what is its type? value -> DataResult.success(this.typeFunction.apply(value)), // look up the codec for a type or its an error type -> Optional.ofNullable(this.CODECS.get(type)).map(DataResult::success).orElse(DataResult.error("Unknown type " + type))); } public void register(TYPE type, Codec<? extends VALUE> codec) { this.CODECS.put(type, codec); } public static void main(String[] args) { // type is the class's simple name var registry = new TypedCodecRegistry<>(Codec.STRING, value -> value.getClass().getSimpleName()); var codec = registry.codec(); registry.register(Integer.class.getSimpleName(), Codec.INT); registry.register(String.class.getSimpleName(), Codec.STRING); registry.register(ResourceLocation.class.getSimpleName(), ResourceLocation.CODEC); System.out.println(encode(codec, "hello world")); System.out.println(encode(codec, 42)); System.out.println(encode(codec, new ResourceLocation("minecraft:dirt"))); } public static String encode(Codec<Object> codec, Object value) { return codec.encodeStart(JsonOps.INSTANCE, value).getOrThrow(false, System.err::println).toString(); } } It outputs Quote {"type":"String","value":"hello world"} {"type":"Integer","value":42} {"type":"ResourceLocation","value":"minecraft:dirt"} Boilerplate: If you don't post your logs/debug.log we can't help you. For curseforge you need to enable the forge debug.log in its minecraft settings. You should also post your crash report if you have one. If there is no error in the log file and you don't have a crash report then post the launcher_log.txt from the minecraft folder. Again for curseforge this will be in your curseforge/minecraft/Install Large files should be posted to a file sharing site like https://gist.github.com You should also read the support forum sticky post.
December 19, 20222 yr Author Not exactly that, I most likely worded that poorly. I have already implemented different codec mappings using the #dispatch functions, this question is more about codec creation for one value. I'll try to explain my current code in greater detail. I currently have one registry of objects which are called RewardTransformerType and it's class looks like this public final class RewardTransformerType<V, T extends RewardTransformer<V>> implements IdentifierHolder, Predicate<Class<?>> { public static final Codec<RewardTransformer<?>> CODEC = QuestingRegistries.REWARD_TRANSFORMERS.dispatch("type", RewardTransformer::getType, type -> type.codec); private final ResourceLocation identifier; private final Codec<T> codec; private final Class<V> type; public RewardTransformerType(ResourceLocation identifier, Codec<T> codec, Class<V> type) { this.identifier = identifier; this.codec = codec; this.type = type; } @Override public ResourceLocation getIdentifier() { return identifier; } @Override public boolean test(Class<?> aClass) { return aClass.equals(this.type); } } And one type implementation which looks like this public class CountByAttributeTransformer implements RewardTransformer<Integer> { public static final Codec<CountByAttributeTransformer> CODEC = RecordCodecBuilder.create(instance -> instance.group( OutputModifier.CODEC.fieldOf("modifier").forGetter(t -> t.modifier) ).apply(instance, CountByAttributeTransformer::new)); private final OutputModifier modifier; public CountByAttributeTransformer(OutputModifier modifier) { this.modifier = modifier; } @Override public Integer adjust(Integer originalValue, Player player, Quest quest) { return PlayerData.get(player).map(data -> { IAttributeProvider provider = data.getAttributes(); return (int) Math.round(this.modifier.getModifiedValue(provider, originalValue)); }).orElse(originalValue); } @Override public RewardTransformerType<?, ?> getType() { return QuestRegistry.COUNT_BY_ATTRIBUTE_TRANSFORMER; } } As you can see, currently the Codec for CountByAttributeTransformer class uses codec from OutputModifier class, which looks like this public static final Codec<OutputModifier> CODEC = RecordCodecBuilder.create(instance -> instance.group( ResourceLocation.CODEC.flatXmap(location -> { IAttributeId id = Attribs.find(location); return id == null ? DataResult.error("Unknown attribute " + location) : DataResult.success(id); }, attributeId -> attributeId == null ? DataResult.error("Attribute is null") : DataResult.success(attributeId.getId())) .fieldOf("attribute").forGetter(t -> t.attributeId), ResourceLocation.CODEC.flatXmap(location -> { IModifierOp op = AttributeOps.find(location); return op == null ? DataResult.error("Unknown operation " + location) : DataResult.success(op); }, operation -> operation == null ? DataResult.error("Operation is null") : DataResult.success(operation.getId())) .fieldOf("operation").forGetter(t -> t.operator) ).apply(instance, OutputModifier::new)); My problem with this is that when I want to serialize/deserialize it, result looks like this { "type": "namespace:id", "modifier": { "attribute": "namespace:id", "operation": "namespace:id" } } and I would like to get this result instead { "type": "namespace:id", "attribute": "namespace:id", "operation": "namespace:id" } So my question is how to adjust the codec in CountByAttribute class so I can get the wanted result (if it is even possible). I have also tried using this codec, but that was resulting in "Not an JSON object" error, so thats wrong approach too public static final Codec<CountByAttributeTransformer> CODEC = OutputModifier.CODEC.xmap(CountByAttributeTransformer::new, t -> t.modifier); Is there a way to get to the second result (without creating duplicate codec for OutputModifier class)? Edited December 19, 20222 yr by Toma™
December 19, 20222 yr Can you please stop posting snippets and show all the relevant code (preferably on github), so I don't have to guess what you are doing and waste my time like I did above. e.g. (and it is only one example) what does this do: Quote QuestingRegistries.REWARD_TRANSFORMERS Without seeing all the code, I can't tell why your xmap solution doesn't work, you don't show where/how it is referenced/used. Boilerplate: If you don't post your logs/debug.log we can't help you. For curseforge you need to enable the forge debug.log in its minecraft settings. You should also post your crash report if you have one. If there is no error in the log file and you don't have a crash report then post the launcher_log.txt from the minecraft folder. Again for curseforge this will be in your curseforge/minecraft/Install Large files should be posted to a file sharing site like https://gist.github.com You should also read the support forum sticky post.
Join the conversation
You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.