benwoodworth / knbt Goto Github PK
View Code? Open in Web Editor NEWKotlin NBT library for kotlinx.serialization
License: GNU Lesser General Public License v3.0
Kotlin NBT library for kotlinx.serialization
License: GNU Lesser General Public License v3.0
Seems like an issue with Kotlin/JS itself, since all numbers are stored as Doubles in Javascript: KT-35422
BinaryNbtReaderTest.Should_decode_uncompressed_bigtest_nbt_to_class_correctly failed:
Structures are not equal.
level.floatTest: Expected <0.49823147>, actual <0.4982314705848694>.
NBT doesn't have a tag type for single characters, but it makes sense to follow kotlinx-serialization's lead with how it handles Chars in JSON. It parses as a string (or other primitive) and fails if the length isn't 1
Char could also be encoded as a UShort
since the bitlength is the same, but that doesn't seem right semantically. If someone opens the serialized NBT in an NBT viewer, the encoded char would be a number, whereas a string would show correctly
Minecraft appears to encode all enum-like values as an ordinal, so consider doing the same with enum values instead of throwing an "unsupported" error.
In v0.11, UByteArray
, UIntArray
, and ULongArray
all serialize to NbtList
s, unlike normal signed Arrays
, which serialize to their Nbt*Array
counterparts
If you have any use cases please drop them here!
I'm pretty busy and not even sure polymorphic serialization is needed for NBT so unless there's a need for it I probably won't get around to it for a while
Currently the encoder is happy to print out NaNd
, -Infinityd
, etc. but there's nothing in the decoder to pick those up. It gets read in as NbtString
s.
And interestingly, Java Minecraft seems to have the same behavior
It'd be cool to have a DSL that allows you to access specifc properties of an arbitrary NBT source without needing a model class. Similar to how KotlinNBT does it or how I plan to do it in my version.
The DSL definitions for KotlinNBT can be found here. An example of this DSL would be:
val fibonacci: IntArray = nbt["testCompound"].asTagCompound["fibonacciWithoutZero"].asIntArray
val message: String = nbt["testList"].asTagList[0].asTagCompound["firstString"].asString
val timestamp: Long = nbt["timestamp"].asLong
The DSL definitions for KXSmine can be found here. An example of this DSL would be:
val fibonacci = root.compound("testCompound").intarray("fibonacciWithoutZero").data
val message = root.list("testList").compoundAt(0).string("firstString").data
val timestamp = root.long("timestamp").data
The main benefit from such a DSL would be the ability to type-safely access just specific properties without having to write a whole model class for the NBT source you're parsing.
Some utility methods/properties could be added on kotlin classes to simplify creation of Nbt types.
I actually have some methods listed in the examples on some projects and I use them often.
Exemples :
"test".nbt
to transform a String
to a NbtString
.true.asNbtByte()
to transform a Boolean
to a NbtByte
.listOf(1, 2, 3).asNbtList()
to transform a List
of Number
to the NbtList
version, maybe to the Nbt[Number]Array
version when possible.Currently Nbt
implements BinaryFormat
and StringFormat
, but it might make more sense for them to be separate
It would be nice to have default functionality common to binary/stringified NBT accessible with Nbt.[...]
, but the current NbtFormat
/Nbt
/StringifiedNbt
naming doesn't work nicely with that.
Brief rationale:
Nbt.[...]
for the API feels nicer from a naming perspective for basic un-configured serializationNbt
class
NbtFormat
, and it feels awkward/unexpected to be a different configuration typeNbtFormat
interface as a companion object
NbtFormat.encodeToNbtTag(serializer, value)
Nbt
class represent the general structure for the format, dealing with the in-memory representation and the NbtTag
classes.
Nbt
named according to what functionality they bring (BinaryNbt
and StringifiedNbt
).Nbt-
, while binary/stringified API are BinaryNbt-
and StringifiedNbt-
, making the API easier to find through intellisense, and grouping code files more nicely when alphatized.E.g.
@Serializable
data class NbtMyIntArray(
@NbtType(NbtIntArray::class)
val intArray: List<Int> // Encode as `NbtIntArray` instead of `NbtList<NbtInt>`
)
I have found a problem regarding the poymorphic serialization in the default serializer. When encoding a serializable marked sealed class, it throws an exception with the following stack trace:
Exception in thread "main" java.lang.ClassCastException: class kotlinx.serialization.descriptors.PolymorphicKind$SEALED cannot be cast to class kotlinx.serialization.descriptors.StructureKind (kotlinx.serialization.descriptors.PolymorphicKind$SEALED and kotlinx.serialization.descriptors.StructureKind are in unnamed module of loader 'app')
at net.benwoodworth.knbt.internal.DefaultNbtEncoder.encodeElement(DefaultNbtEncoder.kt:27)
at kotlinx.serialization.encoding.AbstractEncoder.encodeStringElement(AbstractEncoder.kt:65)
at kotlinx.serialization.internal.AbstractPolymorphicSerializer.serialize(AbstractPolymorphicSerializer.kt:34)
at kotlinx.serialization.encoding.Encoder$DefaultImpls.encodeSerializableValue(Encoding.kt:282)
at kotlinx.serialization.encoding.AbstractEncoder.encodeSerializableValue(AbstractEncoder.kt:18)
at net.benwoodworth.knbt.AbstractNbtEncoder.encodeSerializableValue(NbtEncoder.kt:100)
at kotlinx.serialization.encoding.AbstractEncoder.encodeSerializableElement(AbstractEncoder.kt:80)
at Person$Child.write$Self(NbtTest.kt:16)
at Person$Child$$serializer.serialize(NbtTest.kt:16)
at Person$Child$$serializer.serialize(NbtTest.kt:16)
at kotlinx.serialization.encoding.Encoder$DefaultImpls.encodeSerializableValue(Encoding.kt:282)
at kotlinx.serialization.encoding.AbstractEncoder.encodeSerializableValue(AbstractEncoder.kt:18)
at net.benwoodworth.knbt.AbstractNbtEncoder.encodeSerializableValue(NbtEncoder.kt:100)
at kotlinx.serialization.encoding.AbstractEncoder.encodeSerializableElement(AbstractEncoder.kt:80)
at net.benwoodworth.knbt.internal.RootClassSerializer.serialize(RootClassSerializer.kt:23)
at kotlinx.serialization.encoding.Encoder$DefaultImpls.encodeSerializableValue(Encoding.kt:282)
at kotlinx.serialization.encoding.AbstractEncoder.encodeSerializableValue(AbstractEncoder.kt:18)
at net.benwoodworth.knbt.AbstractNbtEncoder.encodeSerializableValue(NbtEncoder.kt:100)
at net.benwoodworth.knbt.NbtFormatKt.encodeToNbtWriter(NbtFormat.kt:49)
at net.benwoodworth.knbt.Nbt.encodeToSink(Nbt.kt:25)
at net.benwoodworth.knbt.JvmStreamsKt.encodeToStream(JvmStreams.kt:18)
at NbtTestKt.main(NbtTest.kt:46)
at NbtTestKt.main(NbtTest.kt)
When looking at the line that threw the exception, it shows this:
when (descriptor.kind as StructureKind) {
//...
}
The reason why it throws the error is because descriptor.kind is of type SerialKind, which is in fact a supertype of StructureKind, however, the type of descriptor.kind at this point is PolymorphicKind, which is not a subtype of StructureKind. They are on different parts of the hierarchy.
SerialKind
| - PolymorphicKind
| - ...
|- StructureKind
|- ...
My Nbt object setup looks like this:
val nbt = Nbt {
variant = NbtVariant.Java
compression = NbtCompression.None
compressionLevel = null
encodeDefaults = false
ignoreUnknownKeys = false
serializersModule = SerializersModule {
polymorphic(DataElement::class){
subclass(DataElement.Section::class)
subclass(DataElement.Group::class)
subclass(DataElement.Unit::class)
}
}
}
However, this also crashes if I use the EmptySerializersModule.
I was able to reproduce it with the following code:
import kotlinx.serialization.decodeFromByteArray
import kotlinx.serialization.encodeToByteArray
import kotlinx.serialization.modules.EmptySerializersModule
import net.benwoodworth.knbt.*
import okio.use
import java.io.File
@kotlinx.serialization.Serializable
sealed class Person{
abstract val name: String
abstract val age: Int
@kotlinx.serialization.Serializable
class Adult(override val name: String, override val age: Int, val occupancy: String): Person()
@kotlinx.serialization.Serializable
class Teen(override val name: String, override val age: Int, val school: String): Person()
@kotlinx.serialization.Serializable
class Child(override val name: String, override val age: Int, val father: Person, val mother: Person): Person()
}
fun main(){
val nbt = Nbt {
variant = NbtVariant.Java // Java, Bedrock, BedrockNetwork
compression = NbtCompression.None // None, Gzip, Zlib
compressionLevel = null // in 0..9
encodeDefaults = false
ignoreUnknownKeys = false
serializersModule = EmptySerializersModule
}
val father = Person.Adult("jon", 32, "accountant")
val mother = Person.Adult("kathy", 31, "baker")
val child = Person.Child("nate", 6, father, mother)
val fs = File("nbt.test")
val childBA = nbt.encodeToByteArray(child)
// fs.outputStream().use {
// nbt.encodeToStream(child, it)
// }
// val childFromNbt: Person.Child = fs.inputStream().use {
// nbt.decodeFromStream(it)
// }
val childFromNbt: Person.Child = nbt.decodeFromByteArray(childBA)
println(childFromNbt)
The okio code which is commented was the original code I used to reproduce it but I decided tried it for just a regular bytearray encode/decode call. Of course it would be same because this crash is in the default nbt encoder.
Currently (v0.11) classes are serialized within a single-entry NbtCompound, with the serial name as the key. But, only if serialized at the root.
This behavior is convenient, but has a couple issues:
@SerialName
doesn't support empty strings, which is common in NBT files.
NbtContentPolymorphicSerializer
, because extra nesting is added for a class depending on if it's at the root, looking at the shape of the tag alone isn't enough, and properly accounting for it is undesirable complexity.Nbt.encodeToNbtTag(someClass)
and encoder.encodeSerializableElement(serializer, someClass)
don't produce the same result.
This issue's initial redesign has been implemented, but is being replaced by the new design proposed in this comment
NbtCompound
An NBT file consists of a single GZIPped Named Tag of type TAG_Compound.
@NbtNamed
might be a good annotation name. @NbtRoot
and @NbtFile
have been used in earlier versions of knbt, but they seem too specificStructureKind.CLASS
/OBJECT
class
nesting using the serial nameStructureKind.MAP
StructureKind.LIST
This design aims to add a concept of NBT names that all serializable types have, with every type either:
The initial implementation will mainly focus on implementing static naming, since most use cases don't need logic around the serialized NBT names, and it's also not clear yet how an API for dynamic names should behave. See the dynamic names section below for details.
For the scope of this initial design, the NBT name only applies to the root tag name. Though in the future, especially with dynamic names, it's possible the NBT name could apply more broadly. (e.g. a value within an NBT compound knowing its own element name while deserializing)
These are use cases that are being designed for in this new approach
@NbtName("")
Json
is strict by default, e.g. with isLenient
and ignoreUnknownKeys
ignoreNbtName
configuration option or similar@NbtName
(or including it in the serial descriptor annotations)val decodedNbt: NbtNamed<NbtTag> = nbt.decodeFromBufferedSource(source)
val encodedNbt: NbtNamed<NbtTag> = nbt.encodeToNamedNbtTag(data)
Only some variants of NBT have root names encoded. With this new design, serializing values should be the same between named and unnamed root variants (instead of named variants being modeled as nested in an NBT compound).
Unnamed root variants:
net.minecraft.data.Main
NBT -> SNBT conversion tool, since its output SNBT doesn't include the root name anywhere)NbtTag
s@NbtName
annotation, on classes or in serial descriptorsAdd a basic NbtNamed
class for an in-memory representation of the (root) NBT value and its name.
class NbtNamed<T>(val name: String, val value: T)
T
, since the newer Java Network NBT supports null
(TAG_End
) as a root valueNbtNamed
's serializer will be specially handled by the NBT encoder/decoder for nowNbtNamed
's serializer
NbtNamed
should override that value's NBT name
NbtNamed
(directly, or indirectly when delegating serializers) should give precedence to the outermost dynamic nameCare was taken when deciding how this would work, making sure there's room for full-blown dynamically serialized names to be added later in a forward-compatible way.
NbtNamed
at the root for now
TAG_Compound
entries as being a list of named tags, so there is an interpretation where it makes sense for NBT names` to be used with nested tag entriesList
instead of Array
types (generally a good practice in Kotlin)NbtList
s more easily
Nbt*Array
types be constructed from lists
NbtArray
s was not possible and required copying to new arraysNbtIntArray(intArray.asList())
BinaryNbtReaderTest.should_decode_to_class_correctly:
> Task :mingwX64ProcessResources NO-SOURCE
> Task :mingwX64MainKlibrary
> Task :compileTestKotlinMingwX64
> Task :linkDebugTestMingwX64
Expiring Daemon because JVM heap space is exhausted
Daemon will be stopped at the end of the build after running out of JVM memory
Expiring Daemon because JVM heap space is exhausted
Expiring Daemon because JVM heap space is exhausted
> Task :mingwX64Test FAILED
35 tests completed, 1 failed
net.benwoodworth.knbt.internal.BinaryNbtReaderTest.should_decode_to_class_correctly FAILED
Unknown
4 actionable tasks: 4 executed
FAILURE: Build failed with an exception.
* What went wrong:
Execution failed for task ':mingwX64Test'.
> Test running process exited unexpectedly.
Current test: should_decode_to_class_correctly
* Try:
> Run with --stacktrace option to get the stack trace.
> Run with --info or --debug option to get more log output.
> Run with --scan to get full insights.
* Get more help at https://help.gradle.org/
BUILD FAILED in 4m
Error: Process completed with exit code 1.
When decoding Nbt from a stream, the decoder will read past the point where the actual Nbt data is.
This is generally not an issue if you're solely reading the Nbt data, or if it's the last in a sequence of reads, but becomes a problem when you need to perform subsequent reads after decoding the Nbt.
Here's an example to reproduce the problem:
val nbt = Nbt {
variant = NbtVariant.JavaNetwork(764)
compression = NbtCompression.None
}
//A stream of data simulating the structure of a packet in the protocol
//NbtCompound ({text:"test"}), followed by an int (3) and a byte (5)
val array = byteArrayOf(10, 8, 0, 4, 116, 101, 120, 116, 0, 4, 116, 101, 115, 116, 0, 0, 0, 0, 3, 5)
val input = ByteArrayInputStream(array)
val tag: NbtTag = nbt.decodeFromStream(input)
println(tag)
println(input.available())
The expected output would be the following, since only the tag was read:
{text:"test"}
5
But I'm getting:
{text:"test"}
0
The title pretty much sums it up: Allow the end tag as root to represent an element that is not present, which would allow the serialization of nullable types at root. This is a behavior that already exists on some parts of the Vanilla client/server (item nbts, query responses, among others), so there is an argument in support of making this a more general behavior.
I'm currently using a workaround to make it possible. But it's quite hacky to have to do this from outside the library, since it involves having to read ahead and then rolling back depending on the type of tag.
I was trying to parse level.dat
to get the level name, but it turns out that it doesn't work with the models
@OptIn(ExperimentalNbtApi::class)
@NbtRoot(name = "")
@Serializable
data class LevelRoot(
@SerialName("Data")
val data: LevelData
)
@SerialName("Data")
@Serializable
data class LevelData(
@SerialName("LevelName")
val levelName: String
)
and the config
Nbt {
variant = NbtVariant.Java
compression = NbtCompression.Gzip
ignoreUnknownKeys = true
}
The following exception + stacktrace is thrown:
Exception in thread "main" java.lang.IllegalStateException: Unexpected TAG_End
at net.benwoodworth.knbt.internal.NbtReaderKt.discardTag(NbtReader.kt:203)
at net.benwoodworth.knbt.internal.ClassNbtDecoder.handleUnknownKey(NbtDecoder.kt:253)
at net.benwoodworth.knbt.internal.ClassNbtDecoder.decodeElementIndex(NbtDecoder.kt:269)
at msw.server.core.model.world.LevelData$$serializer.deserialize(leveldat.kt:17)
at msw.server.core.model.world.LevelData$$serializer.deserialize(leveldat.kt:17)
at kotlinx.serialization.encoding.Decoder$DefaultImpls.decodeSerializableValue(Decoding.kt:260)
at kotlinx.serialization.encoding.AbstractDecoder.decodeSerializableValue(AbstractDecoder.kt:16)
at net.benwoodworth.knbt.AbstractNbtDecoder.decodeSerializableValue(NbtDecoding.kt:84)
at net.benwoodworth.knbt.internal.BaseNbtDecoder.decodeSerializableValue(NbtDecoder.kt:177)
at kotlinx.serialization.encoding.AbstractDecoder.decodeSerializableValue(AbstractDecoder.kt:43)
at net.benwoodworth.knbt.internal.BaseNbtDecoder.decodeSerializableValue(NbtDecoder.kt:180)
at kotlinx.serialization.encoding.AbstractDecoder.decodeSerializableElement(AbstractDecoder.kt:70)
at msw.server.core.model.world.LevelRoot$$serializer.deserialize(leveldat.kt:8)
at msw.server.core.model.world.LevelRoot$$serializer.deserialize(leveldat.kt:8)
at kotlinx.serialization.encoding.Decoder$DefaultImpls.decodeSerializableValue(Decoding.kt:260)
at kotlinx.serialization.encoding.AbstractDecoder.decodeSerializableValue(AbstractDecoder.kt:16)
at net.benwoodworth.knbt.AbstractNbtDecoder.decodeSerializableValue(NbtDecoding.kt:84)
at net.benwoodworth.knbt.internal.BaseNbtDecoder.decodeSerializableValue(NbtDecoder.kt:177)
at kotlinx.serialization.encoding.AbstractDecoder.decodeSerializableValue(AbstractDecoder.kt:43)
at net.benwoodworth.knbt.internal.BaseNbtDecoder.decodeSerializableValue(NbtDecoder.kt:180)
at kotlinx.serialization.encoding.AbstractDecoder.decodeSerializableElement(AbstractDecoder.kt:70)
at kotlinx.serialization.encoding.CompositeDecoder$DefaultImpls.decodeSerializableElement$default(Decoding.kt:535)
at net.benwoodworth.knbt.NbtRootDeserializer.deserialize(NbtRoot.kt:66)
at kotlinx.serialization.encoding.Decoder$DefaultImpls.decodeSerializableValue(Decoding.kt:260)
at kotlinx.serialization.encoding.AbstractDecoder.decodeSerializableValue(AbstractDecoder.kt:16)
at net.benwoodworth.knbt.AbstractNbtDecoder.decodeSerializableValue(NbtDecoding.kt:84)
at net.benwoodworth.knbt.internal.BaseNbtDecoder.decodeSerializableValue(NbtDecoder.kt:177)
at net.benwoodworth.knbt.NbtFormatKt.decodeFromNbtReader(NbtFormat.kt:67)
at net.benwoodworth.knbt.Nbt.decodeFromSource(Nbt.kt:41)
at net.benwoodworth.knbt.JvmStreamsKt.decodeFromStream(JvmStreams.kt:35)
at [invocation of Nbt.decodeFromStream<LevelRoot>(inputStream)]
I found out that this exception is thrown when
ignoreUnknownKeys
is true
I started with testing this with level.dat
, trying to access [root]/Data/LevelName
. That didn't work because there are other keys after LevelName
that are not present in the model. Then I tested it with a constructed nbt file that basically just contained
[root]: {
Data: {
LevelName: "test"
}
}
This could be deserialized into the models. Then, I added a random key that is not present in the models:
[root]: {
Data: {
ScheduledEvents: []
LevelName: "test"
}
}
This still works, since ignoreUnknownKeys
is turned on. However, when I swap the positions of ScheduledEvents
and LevelName
:
[root]: {
Data: {
LevelName: "test"
ScheduledEvents: []
}
}
The above error is thrown.
Kotlin 1.5.30
KXS 1.2.2
knbt 0.9.1
Similar to kotlinx.serialization's JsonContentPolymorphicSerializer
(here)
Currently (v0.11), the NbtList
/Array
classes all implement the List
interface, following the example that kotlinx-serialization-json
set with JsonArray
(implementing List
for convenience).
https://github.com/Kotlin/kotlinx.serialization/blob/8a2c1c0e05ac9c77746141837f6d53d923e24d8a/formats/json/commonMain/src/kotlinx/serialization/json/JsonElement.kt#L191-L195
Its equals
implementation simply compares the content, following the contract for list equality. From the List.equals() javadoc:
...two lists are defined to be equal if they contain the same elements in the same order. This definition ensures that the equals method works properly across different implementations of the List interface.
However, there's a problem that NBT is faced with, having four list-like types. Comparing NbtList == NbtIntArray
, you would always expect that to be false
, as they represent two different serialized forms. But, according to the List
contract, an empty NbtList
should equal an empty NbtIntArray
, since they have the same content.
In v0.11, this is worked around by special-casing NbtTag comparisons, and only checking for the same NbtTag
type if both are NbtTag
s. E.g. NbtList.equals()
:
knbt/src/commonMain/kotlin/NbtTag.kt
Lines 125 to 129 in dba146f
So, for empty list
/nbtList
/nbtIntArray
s:
List
contract is satisfied for non-NBT types: list == nbtList
and list == nbtIntArray
nbtList != nbtIntArray
This approach satisfies all of the requirements in the Any.equals() contract, except transitive:
- Reflexive: for any non-null value x, x.equals(x) should return true.
- Symmetric: for any non-null values x and y, x.equals(y) should return true if and only if y.equals(x) returns true.
- Transitive: for any non-null values x, y, and z, if x.equals(y) returns true and y.equals(z) returns true, then x.equals(z) should return true.
- Consistent: for any non-null values x and y, multiple invocations of x.equals(y) consistently return true or consistently return false, provided no information used in equals comparisons on the objects is modified.
- Never equal to null: for any non-null value x, x.equals(null) should return false.
Example:
nbtList == list
, andlist == NbtIntArray
, so ideallynbtList == nbtIntArray
, but it doesn't.Collection
instead (or nothing at all, similar to Kotlin's Array classes)
List
equality contractTAG_List
s can have a different element type encoded)NbtList
/Nbt*Array.asList()
extensions)
Lists
List
NbtCompound
not implement Map
for similar reasonsequals(NbtTag?)
overload to NbtTag
List
Any?.equals()
stdlib extension (since a similar NbtTag?.equals()
would need to be explicitly imported, and that seems like a potentially major source of issue)equals
in general feels like a design smell, since values are either equal or they aren't, and it doesn't seem like something the caller should be concerned withList
consumers, albeit in potentially uncommon casesAfter having thought on this for admittedly way too much time, I'm leaning towards the first option and implementing nothing for the list-like types (and NbtCompound
as well for consistency), leaving richer functionality to the kotlin stdlib List/Maps, and bridging the gap in convenience with asList()
and asMap()
extensions. Plus adding basic members like size
and get()
that are often used at the deserialization boundary.
NBT in Minecraft Java is implemented with Java's DataInput.readUTF(String), and DataInput/Output represent strings with Modified UTF-8 which is slightly different than standard UTF-8 for 0x00 and higher codepoints
Would have the same functionality as @SerialName
, but takes precedence when serializing NBT
see: Kotlin/kotlinx.serialization#2104
@NbtSerialName
@NbtName
(I checked all officially/community supported formats for related @SerialInfo annotation class
es)
Mirror kotlinx.serialization's Okio API once released:
Kotlin/kotlinx.serialization#1901
Remove NBT-specific begin*
functions, and leave the determination up to the serialization process based on the SerialDescriptor
Currently only the bytes 0b
and 1b
can be read for boolean values, with all others throwing a serialization exception.
However, Minecraft is more lenient and parses everything but 0b
as true
, with no values throwing an exception.
This can be useful for more efficient switching in when
statements (with enum when
s compiling to branch tables on JVM), it can be useful for exposing the specific TAG_List
element type (which could be TAG_End
or something else for empty lists), and acts as a standard way to work with the tag types instead of library users having to create their own enum.
Currently, two binary different NaN
values will return false
for equality checks, even though they serialize differently.
Since the NbtTag types represent the serial form, if the serialized value is different, then the serial form should be unequal.
So, these equal functions should compare by the binary representation (with .toRawBits()
)
If you attempt to serialize an NbtTag
and have a subclass of that tag, a SerializationException
occurs.
Exception in thread "main" kotlinx.serialization.SerializationException: Class 'NbtCompound' is not registered for polymorphic serialization in the scope of 'NbtTag'.
Mark the base class as 'sealed' or register the serializer explicitly.
at kotlinx.serialization.internal.AbstractPolymorphicSerializerKt.throwSubtypeNotRegistered(AbstractPolymorphicSerializer.kt:102)
at kotlinx.serialization.internal.AbstractPolymorphicSerializerKt.throwSubtypeNotRegistered(AbstractPolymorphicSerializer.kt:113)
at kotlinx.serialization.PolymorphicSerializerKt.findPolymorphicSerializer(PolymorphicSerializer.kt:109)
at kotlinx.serialization.json.internal.StreamingJsonEncoder.encodeSerializableValue(StreamingJsonEncoder.kt:229)
at kotlinx.serialization.encoding.Encoder$DefaultImpls.encodeNullableSerializableValue(Encoding.kt:299)
at kotlinx.serialization.encoding.AbstractEncoder.encodeNullableSerializableValue(AbstractEncoder.kt:18)
at kotlinx.serialization.encoding.AbstractEncoder.encodeNullableSerializableElement(AbstractEncoder.kt:90)
at kotlinx.serialization.json.internal.StreamingJsonEncoder.encodeNullableSerializableElement(StreamingJsonEncoder.kt:154)
at kotlinx.serialization.json.internal.StreamingJsonEncoder.encodeSerializableValue(StreamingJsonEncoder.kt:224)
at kotlinx.serialization.internal.NullableSerializer.serialize(NullableSerializer.kt:23)
at kotlinx.serialization.json.internal.StreamingJsonEncoder.encodeSerializableValue(StreamingJsonEncoder.kt:224)
at kotlinx.serialization.encoding.AbstractEncoder.encodeSerializableElement(AbstractEncoder.kt:80)
at kotlinx.serialization.internal.CollectionLikeSerializer.serialize(CollectionSerializers.kt:69)
at kotlinx.serialization.json.internal.StreamingJsonEncoder.encodeSerializableValue(StreamingJsonEncoder.kt:224)
at kotlinx.serialization.encoding.AbstractEncoder.encodeSerializableElement(AbstractEncoder.kt:80)
at io.github.gaming32.mckt.PlayerData.write$Self(world.kt:494)
at io.github.gaming32.mckt.PlayerData$$serializer.serialize(world.kt:494)
at io.github.gaming32.mckt.PlayerData$$serializer.serialize(world.kt:494)
at kotlinx.serialization.json.internal.StreamingJsonEncoder.encodeSerializableValue(StreamingJsonEncoder.kt:224)
at kotlinx.serialization.json.internal.JsonStreamsKt.encodeByWriter(JsonStreams.kt:28)
at kotlinx.serialization.json.JvmStreamsKt.encodeToStream(JvmStreams.kt:25)
... 19 definitely unrelated frames
Currently shared platform code uses symlinks to work around a limitation in Kotlin 1.5's MPP. This workaround shouldn't be needed anymore
Similar to kotkinx.serialization's JsonTransformingSerializer
It would be extremely useful to have data structure-based tags (such as NbtCompound
) be mutable.
Hi, for a specific reason, I have to serialize an object as a JSON that also contains a NbtTag property, but I can't figure out how to make it works. By having the default Serializer I get the following error :
Class 'NbtList' is not registered for polymorphic serialization in the scope of 'NbtTag'.
Mark the base class as 'sealed' or register the serializer explicitly.
As of 23w31a, NBT tags sent over the network no longer encode the usual "empty tag name", and instead only the tag type id and payload. A comparison can be seen here.
A configuration option to not encode the root tag's name should be included in the library, but I'm unsure what would be the best approach. Here are some ideas and arguments in favor/against:
NbtVariant
(such as NbtVariant.JavaNetwork
). This would make sense since the change only affects tags sent over the network, while the ones saved to the disk remain the same. However, it could cause confusion since this changes only affects 23w31a and beyond, while prior versions remain unaffected.encodeRootTagName = true
). This would eliminate the necessity for a NbtVariant that would be version dependent. However, this option wouldn't make sense for other NbtVariants aside from Java.Currently the published knbt-native can only be used on linux_x64 (since I'm publishing it from my linux machine)
This should work fine for other platforms, since the only platform-specific dependencies are okio and platform.posix.zlib (which both support most native targets)
I get this error when trying to use this library on Windows (mingw_x64):
Execution failed for task ':compileKotlinNative'.
> Could not resolve all files for configuration ':nativeCompileKlibraries'.
> Could not resolve net.benwoodworth.knbt:knbt:0.3.0.
Required by:
project :
> No matching variant of net.benwoodworth.knbt:knbt:0.3.0 was found. The consumer was configured to find a usage of 'kotlin-api' of a component, as well as attribute 'org.jetbrains.kotlin.platform.type' with value 'native', attribute 'org.jetbrains.kotlin.native.target' with value 'mingw_x64' but:
- Variant 'commonMainMetadataElements' capability net.benwoodworth.knbt:knbt:0.3.0 declares a usage of 'kotlin-api' of a component:
- Incompatible because this component declares a component, as well as attribute 'org.jetbrains.kotlin.platform.type' with value 'common' and the consumer needed a component, as well as attribute 'org.jetbrains.kotlin.platform.type' with value 'native'
- Other compatible attribute:
- Doesn't say anything about org.jetbrains.kotlin.native.target (required 'mingw_x64')
- Variant 'jsApiElements-published' capability net.benwoodworth.knbt:knbt:0.3.0 declares a usage of 'kotlin-api' of a component:
- Incompatible because this component declares a component, as well as attribute 'org.jetbrains.kotlin.platform.type' with value 'js' and the consumer needed a component, as well as attribute 'org.jetbrains.kotlin.platform.type' with value 'native'
- Other compatible attribute:
- Doesn't say anything about org.jetbrains.kotlin.native.target (required 'mingw_x64')
- Variant 'jsRuntimeElements-published' capability net.benwoodworth.knbt:knbt:0.3.0:
- Incompatible because this component declares a usage of 'kotlin-runtime' of a component, as well as attribute 'org.jetbrains.kotlin.platform.type' with value 'js' and the consumer needed a usage of 'kotlin-api' of a component, as well as attribute 'org.jetbrains.kotlin.platform.type' with value 'native'
- Other compatible attribute:
- Doesn't say anything about org.jetbrains.kotlin.native.target (required 'mingw_x64')
- Variant 'jvmApiElements-published' capability net.benwoodworth.knbt:knbt:0.3.0 declares an API of a component:
- Incompatible because this component declares a component, as well as attribute 'org.jetbrains.kotlin.platform.type' with value 'jvm' and the consumer needed a component, as well as attribute 'org.jetbrains.kotlin.platform.type' with value 'native'
- Other compatible attribute:
- Doesn't say anything about org.jetbrains.kotlin.native.target (required 'mingw_x64')
- Variant 'jvmRuntimeElements-published' capability net.benwoodworth.knbt:knbt:0.3.0 declares a runtime of a component:
- Incompatible because this component declares a component, as well as attribute 'org.jetbrains.kotlin.platform.type' with value 'jvm' and the consumer needed a component, as well as attribute 'org.jetbrains.kotlin.platform.type' with value 'native'
- Other compatible attribute:
- Doesn't say anything about org.jetbrains.kotlin.native.target (required 'mingw_x64')
- Variant 'metadataApiElements' capability net.benwoodworth.knbt:knbt:0.3.0:
- Incompatible because this component declares a usage of 'kotlin-metadata' of a component, as well as attribute 'org.jetbrains.kotlin.platform.type' with value 'common' and the consumer needed a usage of 'kotlin-api' of a component, as well as attribute 'org.jetbrains.kotlin.platform.type' with value 'native'
- Other compatible attribute:
- Doesn't say anything about org.jetbrains.kotlin.native.target (required 'mingw_x64')
- Variant 'nativeApiElements-published' capability net.benwoodworth.knbt:knbt:0.3.0 declares a usage of 'kotlin-api' of a component, as well as attribute 'org.jetbrains.kotlin.platform.type' with value 'native':
- Incompatible because this component declares a component, as well as attribute 'org.jetbrains.kotlin.native.target' with value 'linux_x64' and the consumer needed a component, as well as attribute 'org.jetbrains.kotlin.native.target' with value 'mingw_x64'
NBT can either be uncompressed, or compressed with gzip/zlib
Decoding NBT should automatically work.
Encoding NBT can use a compression method configured with the NbtConfiguration
.
Kotlin/JS (and JavaScript in general) will stringify Floats/Doubles differently than on other targets, so make sure the TAG_Float and TAG_Double string representations are compatible with Minecraft's SNBT implementation.
For example, 10^100:
1.0E100
1e+100
This applies to StringifiedNbt
, and to NbtFloat
/NbtDouble.toString()
net
-> com
Becoming difficult to maintain, and the two implementations are very different.
If possible, leaning on a more stateless design and leaning on SerialDescriptor
s would be ideal.
TODO:
SerialDescriptor
should be the source of truth (see: here)A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.