README ¶
NamedBinaryTagParser
As the name suggets, NamedBinaryTagParser is an NBT parser (& now also a serializer), written in Go.
It supports all tag types, as well as being able to intelligently detect and handle gzip & zlib compression (or no compression).
Getting Started
Tag IDs
NamedBinaryTagParser's tag ID constants can be found here.
Tag types
NamedBinaryTagParser's tag types can be found here.
Parsing
Simple
func example(reader io.Reader) {
// create our parser instance
// takes an io.Reader
parser, err := nbt.NewParser(reader)
if err != nil {
panic(err)
}
// the Parser.Read() function will completely parse the NBT compound provided from the reader
// it returns the parsed NBT compound, which is a type alias of map[string]nbt.Tag,
// the name of the outer NBT compound, and also the error if one occurred.
compound, name, err := parser.Read()
if err != nil {
panic(err)
}
// say in our compound we have a Long tag called "my_long"
myTag := compound["my_long"] // this returns the generic tag
// we can verify that the tag is a long
if myTag.Type() != nbt.TagTypeLong {
panic("tag is not a long")
}
// we can now safely cast to a nbt.TagLong, which is a type alias for int64
myLong := myTag.(nbt.TagLong)
fmt.Println(myLong)
}
More Complex Example (list of compounds)
func example(reader io.Reader) {
// create our parser instance
parser, err := nbt.NewParser(reader)
if err != nil {
panic(err)
}
// read the NBT compound
compound, name, err := parser.Read()
if err != nil {
panic(err)
}
// say this time we have a list of compounds, where the list is called "my_list"
myTag := compound["my_list"] // this returns the generic tag
// we can verify that the tag is a list
if myTag.Type() != nbt.TagTypeList {
panic("tag is not a list")
}
// cast to nbt.TagList
myList := myTag.(nbt.TagList)
// verify that the elements are compounds
if myList.ElementType != nbt.TagTypeCompound {
panic("elements are not compounds")
}
// myList.Elements is a []nbt.Tag
for _, tag := range myList.Elements {
// we have verified that they are compounds already, so we can cast without further checks
compound := tag.(nbt.TagCompound)
// we can treat the compound like normal now
// for example, i'll retrieve a string called "my_string"
myString, ok := compound["my_string"].(nbt.TagString)
if !ok {
panic("my_string was not a string")
}
fmt.Println(myString)
}
}
Json Example
A small example in which we serialize an NBT compound to a JSON object.
func example(r io.Reader) {
parser, err := nbt.NewParser(r)
if err != nil {
panic(err)
}
tag, name, err := parser.Read()
if err != nil {
panic(err)
}
withName := map[string]interface{}{
name: tag,
}
marshalled, err := json.Marshal(withName)
if err != nil {
panic(err)
}
fmt.Println(string(marshalled))
}
JSON output
```
{
"Level": {
"byteArrayTest (the first 1000 values of (n*n*255+n*7)%100, starting with n=0 (0, 62, 34, 16, 8, ...))": "",
"byteTest": 127,
"doubleTest": 0.4931287132182315,
"floatTest": 0.49823147,
"intTest": 2147483647,
"listTest (compound)": {
"ElementType": 10,
"Elements": [{
"created-on": 1264099775885,
"name": "Compound tag #0"
}, {
"created-on": 1264099775885,
"name": "Compound tag #1"
}]
},
"listTest (long)": {
"ElementType": 4,
"Elements": [11, 12, 13, 14, 15]
},
"longTest": 9223372036854775807,
"nested compound test": {
"egg": {
"name": "Eggbert",
"value": 0.5
},
"ham": {
"name": "Hampus",
"value": 0.75
}
},
"shortTest": 32767,
"stringTest": "HELLO WORLD THIS IS A TEST STRING ÅÄÖ!"
}
}
```
Serialization
When serializing data, if you wish to use compression, you should parse a gzip writer or zlib writer to NamedBinaryTagParser, unlike when parsing data, in which NamedBinaryTagParser automatically detects the compression and applies the correct reader.
Example
func example(w io.Writer) {
// your data set
// this is wiki.vg's bigtest.nbt
data := nbt.TagCompound{
"nested compound test": nbt.TagCompound{
"egg": nbt.TagCompound{
"name": nbt.TagString("Eggbert"),
"value": nbt.TagFloat(0.5),
},
"ham": nbt.TagCompound{
"name": nbt.TagString("Hampus"),
"value": nbt.TagFloat(0.75),
},
},
"intTest": nbt.TagInt(2147483647),
"byteTest": nbt.TagByte(127),
"stringTest": nbt.TagString("HELLO WORLD THIS IS A TEST STRING \xc3\x85\xc3\x84\xc3\x96!"),
"listTest (long)": nbt.TagList{
ElementType: nbt.TagTypeLong,
Elements: []nbt.Tag{
nbt.TagLong(11),
nbt.TagLong(12),
nbt.TagLong(13),
nbt.TagLong(14),
nbt.TagLong(15),
},
},
"doubleTest": nbt.TagDouble(0.49312871321823148),
"floatTest": nbt.TagFloat(0.49823147058486938),
"longTest": nbt.TagLong(9223372036854775807),
"listTest (compound)": nbt.TagList{
ElementType: nbt.TagTypeCompound,
Elements: []nbt.Tag{
nbt.TagCompound{
"created-on": nbt.TagLong(1264099775885),
"name": nbt.TagString("Compound tag #0"),
},
nbt.TagCompound{
"created-on": nbt.TagLong(1264099775885),
"name": nbt.TagString("Compound tag #1"),
},
},
},
"byteArrayTest (the first 1000 values of (n*n*255+n*7)%100, starting with n=0 (0, 62, 34, 16, 8, ...))": nbt.TagByteArray(byteArrayTestData),
"shortTest": nbt.TagShort(32767),
}
// create out writer
writer := nbt.NewWriter(w)
outerName := "Level" // this is the name of the outer NBT compound
if err := writer.Write(data, outerName); err != nil {
panic(err)
}
}
Testing
I have written a test based on wiki.vg's bigtest.nbt. You can run the test with
go test ./test
Benchmarking
I have also written a benchmark based on wiki.vg's bigtest.nbt. You can run the benchmark with
go test ./test -bench .
Results
Running the benchmark on my home PC (R5 3600X, 12GB RAM), I achieved the following results:
$ go test ./test -bench .
goos: linux
goarch: amd64
pkg: github.com/RyanW02/NamedBinaryTagParser/test
BenchmarkParse-12 53491 21447 ns/op
BenchmarkWrite-12 204349 5781 ns/op
PASS
ok github.com/RyanW02/NamedBinaryTagParser/test 8.542s
Todo
- More tests