Supplement document of nbt API

This commit is contained in:
Tnze
2021-07-06 09:41:32 +08:00
parent 870b4b1a0f
commit 08d2c03a58
4 changed files with 98 additions and 31 deletions

View File

@ -14,6 +14,14 @@ func Unmarshal(data []byte, v interface{}) error {
return err return err
} }
// Decode method decodes an NBT value from the reader underline the Decoder into v.
// Internally try to handle all possible v by reflection,
// but the type of v must matches the NBT value logically.
// For example, you can decode an NBT value which root tag is TagCompound(0x0a)
// into a struct or map, but not a string.
//
// This method also return tag name of the root tag.
// In real world, it is often empty, but the API should allows you to get it when ever you want.
func (d *Decoder) Decode(v interface{}) (string, error) { func (d *Decoder) Decode(v interface{}) (string, error) {
val := reflect.ValueOf(v) val := reflect.ValueOf(v)
if val.Kind() != reflect.Ptr { if val.Kind() != reflect.Ptr {
@ -31,7 +39,7 @@ func (d *Decoder) Decode(v interface{}) (string, error) {
// We decode val not val.Elem because the NBTDecoder interface // We decode val not val.Elem because the NBTDecoder interface
// test must be applied at the top level of the value. // test must be applied at the top level of the value.
err = d.unmarshal(val, tagType, tagName) err = d.unmarshal(val, tagType)
if err != nil { if err != nil {
return tagName, fmt.Errorf("nbt: fail to decode tag %q: %w", tagName, err) return tagName, fmt.Errorf("nbt: fail to decode tag %q: %w", tagName, err)
} }
@ -51,7 +59,7 @@ func (d *Decoder) checkCompressed(head byte) (compress string) {
// ErrEND error will be returned when reading a NBT with only Tag_End // ErrEND error will be returned when reading a NBT with only Tag_End
var ErrEND = errors.New("unexpected TAG_End") var ErrEND = errors.New("unexpected TAG_End")
func (d *Decoder) unmarshal(val reflect.Value, tagType byte, tagName string) error { func (d *Decoder) unmarshal(val reflect.Value, tagType byte) error {
u, val := indirect(val, tagType == TagEnd) u, val := indirect(val, tagType == TagEnd)
if u != nil { if u != nil {
return u.Decode(tagType, d.r) return u.Decode(tagType, d.r)
@ -271,13 +279,13 @@ func (d *Decoder) unmarshal(val reflect.Value, tagType byte, tagName string) err
case reflect.Array: case reflect.Array:
if vl := val.Len(); vl < int(listLen) { if vl := val.Len(); vl < int(listLen) {
return fmt.Errorf( return fmt.Errorf(
"TagList %s has len %d, but array %v only has len %d", "TagList has len %d, but array %v only has len %d",
tagName, listLen, val.Type(), vl) listLen, val.Type(), vl)
} }
buf = val buf = val
} }
for i := 0; i < int(listLen); i++ { for i := 0; i < int(listLen); i++ {
if err := d.unmarshal(buf.Index(i), listType, ""); err != nil { if err := d.unmarshal(buf.Index(i), listType); err != nil {
return err return err
} }
} }
@ -302,7 +310,7 @@ func (d *Decoder) unmarshal(val reflect.Value, tagType byte, tagName string) err
} }
field := tinfo.findIndexByName(tn) field := tinfo.findIndexByName(tn)
if field != -1 { if field != -1 {
err = d.unmarshal(val.Field(field), tt, tn) err = d.unmarshal(val.Field(field), tt)
if err != nil { if err != nil {
return fmt.Errorf("fail to decode tag %q: %w", tn, err) return fmt.Errorf("fail to decode tag %q: %w", tn, err)
} }
@ -328,7 +336,7 @@ func (d *Decoder) unmarshal(val reflect.Value, tagType byte, tagName string) err
break break
} }
v := reflect.New(val.Type().Elem()) v := reflect.New(val.Type().Elem())
if err = d.unmarshal(v.Elem(), tt, tn); err != nil { if err = d.unmarshal(v.Elem(), tt); err != nil {
return fmt.Errorf("fail to decode tag %q: %w", tn, err) return fmt.Errorf("fail to decode tag %q: %w", tn, err)
} }
val.SetMapIndex(reflect.ValueOf(tn), v.Elem()) val.SetMapIndex(reflect.ValueOf(tn), v.Elem())
@ -344,7 +352,7 @@ func (d *Decoder) unmarshal(val reflect.Value, tagType byte, tagName string) err
break break
} }
var value interface{} var value interface{}
if err = d.unmarshal(reflect.ValueOf(&value).Elem(), tt, tn); err != nil { if err = d.unmarshal(reflect.ValueOf(&value).Elem(), tt); err != nil {
return fmt.Errorf("fail to decode tag %q: %w", tn, err) return fmt.Errorf("fail to decode tag %q: %w", tn, err)
} }
buf[tn] = value buf[tn] = value

View File

@ -11,6 +11,9 @@ import (
"strings" "strings"
) )
// Marshal is the shortcut of NewEncoder().Encode() with empty tag name.
// Notices that repeatedly init buffers is low efficiency.
// Using Encoder and Reset the buffer in each times is recommended in that cases.
func Marshal(v interface{}) ([]byte, error) { func Marshal(v interface{}) ([]byte, error) {
var buf bytes.Buffer var buf bytes.Buffer
err := NewEncoder(&buf).Encode(v, "") err := NewEncoder(&buf).Encode(v, "")
@ -25,6 +28,10 @@ func NewEncoder(w io.Writer) *Encoder {
return &Encoder{w: w} return &Encoder{w: w}
} }
// Encode encodes v into the writer inside Encoder with the root tag named tagName.
// In most cases, the root tag typed TagCompound and the tag name is empty string,
// but any other type is allowed just because there is valid technically. Once if
// you should pass an string into this, you should get a TagString.
func (e *Encoder) Encode(v interface{}, tagName string) error { func (e *Encoder) Encode(v interface{}, tagName string) error {
val := reflect.ValueOf(v) val := reflect.ValueOf(v)
return e.marshal(val, getTagType(val), tagName) return e.marshal(val, getTagType(val), tagName)

View File

@ -5,35 +5,75 @@ import (
"fmt" "fmt"
) )
//goland:noinspection SpellCheckingInspection func ExampleDecoder_Decode() {
func ExampleUnmarshal() { reader := bytes.NewReader([]byte{
var data = []byte{ 0x0a, // Start TagCompound("")
0x08, 0x00, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x00, 0x09, 0x00, 0x00,
0x42, 0x61, 0x6e, 0x61, 0x6e, 0x72, 0x61, 0x6d, 0x61,
0x08, // TagString("Author"): "Tnze"
0x00, 0x06, 'A', 'u', 't', 'h', 'o', 'r',
0x00, 0x04, 'T', 'n', 'z', 'e',
0x00, // End TagCompound
})
var value struct {
Author string
} }
var Name string decoder := NewDecoder(reader)
_, err := decoder.Decode(&value)
if err := Unmarshal(data, &Name); err != nil {
panic(err)
}
fmt.Println(Name)
// Output: Bananrama
}
func ExampleMarshal() {
var value = struct {
Name string `nbt:"name"`
}{"Tnze"}
data, err := Marshal(value)
if err != nil { if err != nil {
panic(err) panic(err)
} }
fmt.Printf("% 02x ", data) fmt.Println(value)
// Output:
// {Tnze}
}
func ExampleDecoder_Decode_singleTagString() {
reader := bytes.NewReader([]byte{
// TagString
0x08,
// TagName
0x00, 0x04,
0x6e, 0x61, 0x6d, 0x65,
// Content
0x00, 0x09,
0x42, 0x61, 0x6e, 0x61, 0x6e, 0x72, 0x61, 0x6d, 0x61,
})
var Name string
decoder := NewDecoder(reader)
tagName, err := decoder.Decode(&Name)
if err != nil {
panic(err)
}
fmt.Println(tagName)
fmt.Println(Name)
// Output:
// name
// Bananrama
}
func ExampleEncoder_Encode_tagCompound() {
var value = struct {
Name string `nbt:"name"`
}{"Tnze"}
var buff bytes.Buffer
encoder := NewEncoder(&buff)
err := encoder.Encode(value, "")
if err != nil {
panic(err)
}
fmt.Printf("% 02x ", buff.Bytes())
// Output: // Output:
// 0a 00 00 08 00 04 6e 61 6d 65 00 04 54 6e 7a 65 00 // 0a 00 00 08 00 04 6e 61 6d 65 00 04 54 6e 7a 65 00

View File

@ -2,10 +2,13 @@ package nbt
import ( import (
"bytes" "bytes"
"errors"
"io" "io"
"reflect"
"strings" "strings"
) )
// RawMessage stores the raw binary data of NBT.
type RawMessage struct { type RawMessage struct {
Type byte Type byte
Data []byte Data []byte
@ -49,3 +52,12 @@ func (m RawMessage) String() string {
} }
return sb.String() return sb.String()
} }
func (m RawMessage) Unmarshal(v interface{}) error {
d := NewDecoder(bytes.NewReader(m.Data))
val := reflect.ValueOf(v)
if val.Kind() != reflect.Ptr {
return errors.New("nbt: non-pointer passed to Decode")
}
return d.unmarshal(val, m.Type)
}