diff --git a/README.md b/README.md index 0323622e..a531c876 100644 --- a/README.md +++ b/README.md @@ -8,392 +8,536 @@ --- -> **Notice: When decoding, the java version of hessian will default skip and ignore non-exist fields.** -> **From the version of v1.6.0 , dubbo-go-hessian2 will skip non-exist fields too, while that before v1.6.0 will return errors.** - -It's a golang hessian library used by [Apache/dubbo-go](https://github.com/apache/dubbo-go). - -There is a big performance improvement, and some bugs fix for v1.6.0, -thanks to [micln](https://github.com/micln), [pantianying](https://github.com/pantianying), [zonghaishang](https://github.com/zonghaishang), - [willson-chen](https://github.com/willson-chen), [champly](https://github.com/champly). - -## Feature List - -* [All JDK Exceptions](https://github.com/apache/dubbo-go-hessian2/issues/59) -* [Field Alias By Alias](https://github.com/apache/dubbo-go-hessian2/issues/19) -* [Java wrapper type](https://github.com/apache/dubbo-go-hessian2/issues/349) -* [Java Bigdecimal](https://github.com/apache/dubbo-go-hessian2/issues/89) -* [Java Date & Time](https://github.com/apache/dubbo-go-hessian2/issues/90) -* [java8 time.Date](https://github.com/apache/dubbo-go-hessian2/pull/212) -* [java8 java.sql.Time & java.sql.Date](https://github.com/apache/dubbo-go-hessian2/pull/219) -* [java UUID](https://github.com/apache/dubbo-go-hessian2/pull/256) -* [Java Generic Invokation](https://github.com/apache/dubbo-go-hessian2/issues/84) -* [Java Extends](https://github.com/apache/dubbo-go-hessian2/issues/157) -* [Dubbo Attachements](https://github.com/apache/dubbo-go-hessian2/issues/49) -* [Skipping unregistered POJO](https://github.com/apache/dubbo-go-hessian2/pull/128) -* [Emoji](https://github.com/apache/dubbo-go-hessian2/issues/129) - -## hessian type mapping between Java and Go - -Cross languages message definition should be careful, the following situations should be avoided: - -- define object that only exists in a special language -- using various java exceptions (using error code/message instead) - -So we can maintain a cross language type mapping: - -| hessian type | java type | golang type | -|-----------------------------|----------------------|--------------------------------------------------------| -| **null** | null | nil | -| **binary** | byte[] | []byte | -| **boolean** | boolean | bool | -| **date** | java.util.Date | time.Time | -| **double** | double | float64 | -| **int** | int | int32 | -| **long** | long | int64 | -| **string** | java.lang.String | string | -| **list** | java.util.List | slice | -| **map** | java.util.Map | map | -| **object** | custom define object | custom define struct | -| **big decimal** | java.math.BigDecimal | github.com/dubbogo/gost/math/big/Decimal | -| **big integer** | java.math.BigInteger | github.com/dubbogo/gost/math/big/Integer | -| **date** | java.sql.Date | github.com/apache/dubbo-go-hessian2/java_sql_time/Date | -| **date** | java.sql.Time | github.com/apache/dubbo-go-hessian2/java_sql_time/Time | -| **date** | all java8 sdk time | github.com/apache/dubbo-go-hessian2/java8_time | -| **Integer** | java.lang.Integer | *int32 | -| **Byte** | java.lang.Byte | *byte | -| **Short** | java.lang.Short | *int16 | -| **Boolean** | java.lang.Boolean | *bool | -| **Long** | java.lang.Long | *int64 | -| **Float** | java.lang.Float | *float32 | -| **Double** | java.lang.Double | *float64 | -| **Character** | java.lang.Character | *hessian.Rune | -| **OTHER COMMON USING TYPE** | | | - - -## reference - -- [hessian serialization](http://hessian.caucho.com/doc/hessian-serialization.html) - -## Basic Usage Examples - -### Encode To Bytes +A Go implementation of the [Hessian 2.0 serialization](http://hessian.caucho.com/doc/hessian-serialization.html) protocol, enabling seamless cross-language communication between Go and Java applications. Primarily used by [Apache Dubbo-Go](https://github.com/apache/dubbo-go) for RPC serialization. + +## Table of Contents + +- [Features](#features) +- [Installation](#installation) +- [Quick Start](#quick-start) +- [Core Concepts](#core-concepts) + - [The POJO Interface](#the-pojo-interface) + - [Registering Types](#registering-types) +- [Type Mapping (Java <-> Go)](#type-mapping-java---go) + - [Primitive Types](#primitive-types) + - [Java Wrapper Types](#java-wrapper-types) + - [Extended Types](#extended-types) +- [API Reference](#api-reference) + - [Encoder](#encoder) + - [Decoder](#decoder) + - [Registration Functions](#registration-functions) + - [Configuration Functions](#configuration-functions) +- [Usage Examples](#usage-examples) + - [Encoding and Decoding Custom Objects](#encoding-and-decoding-custom-objects) + - [Customizing Field Names with Tags](#customizing-field-names-with-tags) + - [Decoding Field Name Matching Rules](#decoding-field-name-matching-rules) + - [Using a Custom Tag Identifier](#using-a-custom-tag-identifier) + - [Specifying Java Parameter Types (Inheritance)](#specifying-java-parameter-types-inheritance) + - [Working with Java Collections](#working-with-java-collections) + - [Working with Java Enums](#working-with-java-enums) + - [Custom Serializer](#custom-serializer) + - [Strict Mode](#strict-mode) + - [Reusing Encoder/Decoder (Object Pool)](#reusing-encoderdecoder-object-pool) +- [Struct Inheritance](#struct-inheritance) +- [Tools](#tools) +- [Reference](#reference) + +## Features + +- Full Hessian 2.0 protocol implementation (encode/decode) +- Circular reference and object graph support +- [All JDK exceptions](https://github.com/apache/dubbo-go-hessian2/issues/59) (88+ exception types) +- [Java wrapper types](https://github.com/apache/dubbo-go-hessian2/issues/349) (Integer, Long, Boolean, etc.) +- [Java BigDecimal / BigInteger](https://github.com/apache/dubbo-go-hessian2/issues/89) +- [Java Date & Time](https://github.com/apache/dubbo-go-hessian2/issues/90) (java.util.Date, java.sql.Date/Time) +- [Java 8 Time API](https://github.com/apache/dubbo-go-hessian2/pull/212) (LocalDate, LocalDateTime, ZonedDateTime, Instant, Duration, etc.) +- [Java UUID](https://github.com/apache/dubbo-go-hessian2/pull/256) +- [Java collections](https://github.com/apache/dubbo-go-hessian2/issues/84) (HashSet, HashMap, etc.) +- [Java inheritance / extends](https://github.com/apache/dubbo-go-hessian2/issues/157) +- [Field alias via struct tags](https://github.com/apache/dubbo-go-hessian2/issues/19) +- [Generic invocation](https://github.com/apache/dubbo-go-hessian2/issues/84) +- [Dubbo attachments](https://github.com/apache/dubbo-go-hessian2/issues/49) +- [Skipping unregistered POJOs](https://github.com/apache/dubbo-go-hessian2/pull/128) +- [Emoji support in strings](https://github.com/apache/dubbo-go-hessian2/issues/129) +- Custom serializer support +- Strict mode for decoding validation + +> **Note:** From v1.6.0+, the decoder skips non-existent fields (matching Java hessian behavior). Versions before v1.6.0 returned errors for non-existent fields. + +## Installation + +```bash +go get github.com/apache/dubbo-go-hessian2 +``` + +Requires **Go 1.21+**. + +## Quick Start ```go -type Circular struct { - Value - Previous *Circular - Next *Circular +// Define a struct and implement the POJO interface +type User struct { + Name string + Age int32 } -type Value struct { - Num int +func (User) JavaClassName() string { + return "com.example.User" } -func (Circular) JavaClassName() string { - return "com.company.Circular" +// Register the type before encoding/decoding +hessian.RegisterPOJO(&User{}) + +// Encode +user := &User{Name: "Alice", Age: 30} +encoder := hessian.NewEncoder() +encoder.Encode(user) +data := encoder.Buffer() + +// Decode +obj, _ := hessian.NewDecoder(data).Decode() +decoded := obj.(*User) +``` + +## Core Concepts + +### The POJO Interface + +Any Go struct that needs to be serialized as a Java object must implement the `POJO` interface: + +```go +type POJO interface { + JavaClassName() string // Returns the fully qualified Java class name } +``` -c := &Circular{} -c.Num = 12345 -c.Previous = c -c.Next = c +This establishes the mapping between your Go struct and the corresponding Java class. -e := NewEncoder() -err := e.Encode(c) -if err != nil { - panic(err) +### Registering Types + +Before encoding or decoding custom types, you must register them. This is typically done in an `init()` function: + +```go +func init() { + // Register a single type + hessian.RegisterPOJO(&MyStruct{}) + + // Register multiple types at once + hessian.RegisterPOJOs(&TypeA{}, &TypeB{}, &TypeC{}) + + // Register with a custom Java class name (overrides JavaClassName()) + hessian.RegisterPOJOMapping("com.example.CustomName", &MyStruct{}) } +``` -bytes := e.Buffer() +If a type is not registered, the decoder will: +- **Default mode:** Decode unknown objects as `map[interface{}]interface{}` +- **Strict mode:** Return an error + +## Type Mapping (Java <-> Go) + +### Primitive Types + +| Hessian Type | Java Type | Go Type | +|--------------|--------------------|-------------| +| null | null | nil | +| binary | byte[] | []byte | +| boolean | boolean | bool | +| date | java.util.Date | time.Time | +| double | double | float64 | +| int | int | int32 | +| long | long | int64 | +| string | java.lang.String | string | +| list | java.util.List | slice | +| map | java.util.Map | map | +| object | custom class | struct | + +### Java Wrapper Types + +| Java Type | Go Type | +|-----------------------|-----------| +| java.lang.Integer | \*int32 | +| java.lang.Long | \*int64 | +| java.lang.Boolean | \*bool | +| java.lang.Short | \*int16 | +| java.lang.Byte | \*uint8 | +| java.lang.Float | \*float32 | +| java.lang.Double | \*float64 | +| java.lang.Character | \*hessian.Rune | + +### Extended Types + +| Java Type | Go Type / Package | +|-----------------------------|------------------------------------------------------------| +| java.math.BigDecimal | `github.com/dubbogo/gost/math/big` Decimal | +| java.math.BigInteger | `github.com/dubbogo/gost/math/big` Integer | +| java.sql.Date | `github.com/apache/dubbo-go-hessian2/java_sql_time` Date | +| java.sql.Time | `github.com/apache/dubbo-go-hessian2/java_sql_time` Time | +| java.util.UUID | `github.com/apache/dubbo-go-hessian2/java_util` UUID | +| java.util.Locale | `github.com/apache/dubbo-go-hessian2/java_util` Locale | +| Java 8 time types (LocalDate, LocalTime, LocalDateTime, ZonedDateTime, Instant, Duration, Period, etc.) | `github.com/apache/dubbo-go-hessian2/java8_time` | + +> **Tip:** Avoid defining objects that only exist in one language. Use error codes/messages instead of Java exceptions for cross-language communication. + +## API Reference + +### Encoder + +```go +// Create a new encoder +encoder := hessian.NewEncoder() + +// Encode a value (primitives, slices, maps, structs, etc.) +err := encoder.Encode(value) + +// Get the encoded bytes +data := encoder.Buffer() + +// Reset encoder state for reuse +encoder.Clean() + +// Reset encoder state but reuse the underlying buffer +encoder.ReuseBufferClean() ``` -### Decode From Bytes +**Special encoding methods:** ```go -decodedObject, err := NewDecoder(bytes).Decode() -if err != nil { - panic(err) -} -circular, ok := obj.(*Circular) -// ... +// Encode a map as a typed Java object using "_class" key in the map +encoder.EncodeMapClass(map[string]interface{}{"_class": "com.example.Foo", "name": "bar"}) + +// Encode a map as a specific Java class +encoder.EncodeMapAsClass("com.example.Foo", map[string]interface{}{"name": "bar"}) +``` + +### Decoder + +```go +// Standard decoder +decoder := hessian.NewDecoder(data) + +// Strict mode - returns error for unregistered types +decoder := hessian.NewStrictDecoder(data) + +// Decode the next value +obj, err := decoder.Decode() + +// Reset decoder with new data (for reuse with object pools) +decoder.Reset(newData) ``` -## Customize Usage Examples +**Decoder modes:** -#### Encoding filed name +| Constructor | Behavior | +|----------------------------------|----------| +| `NewDecoder(data)` | Decodes unknown objects as maps | +| `NewStrictDecoder(data)` | Returns error for unregistered objects | +| `NewDecoderWithSkip(data)` | Skips non-existent fields | +| `NewCheapDecoderWithSkip(data)` | Poolable decoder, use with `Reset()` | -Hessian encoder default converts filed names of struct to lower camelcase, but you can customize it using `hessian` tag. +### Registration Functions -Example: ```go -type MyUser struct { - UserFullName string `hessian:"user_full_name"` - FamilyPhoneNumber string // default convert to => familyPhoneNumber +hessian.RegisterPOJO(&MyStruct{}) // Register a POJO type +hessian.RegisterPOJOs(&A{}, &B{}) // Register multiple POJOs +hessian.RegisterPOJOMapping("com.example.Name", &Struct{}) // Register with custom Java class name +hessian.RegisterJavaEnum(&MyEnum{}) // Register a Java enum type +hessian.UnRegisterPOJOs(&A{}, &B{}) // Unregister POJOs +hessian.SetCollectionSerialize(&MyHashSet{}) // Register a Java collection type +hessian.SetSerializer("com.example.Foo", &FooSerializer{}) // Register a custom serializer +``` + +### Configuration Functions + +```go +// Change the struct tag used for field name mapping (default: "hessian") +hessian.SetTagIdentifier("json") + +// Look up a registered custom serializer +serializer, ok := hessian.GetSerializer("com.example.Foo") + +// Find class info in the decoder +classInfo := hessian.FindClassInfo("com.example.Foo") +``` + +## Usage Examples + +### Encoding and Decoding Custom Objects + +```go +type Circular struct { + Value + Previous *Circular + Next *Circular } -func (MyUser) JavaClassName() string { - return "com.company.myuser" +type Value struct { + Num int } -user := &MyUser{ - UserFullName: "username", - FamilyPhoneNumber: "010-12345678", +func (Circular) JavaClassName() string { + return "com.company.Circular" } +func init() { + hessian.RegisterPOJO(&Circular{}) +} + +// Encode +c := &Circular{} +c.Num = 12345 +c.Previous = c // circular reference - handled automatically +c.Next = c + e := hessian.NewEncoder() -err := e.Encode(user) +if err := e.Encode(c); err != nil { + panic(err) +} +data := e.Buffer() + +// Decode +obj, err := hessian.NewDecoder(data).Decode() if err != nil { panic(err) } +circular := obj.(*Circular) +fmt.Println(circular.Num) // 12345 ``` -The encoded bytes of the struct `MyUser` is as following: -```text - 00000000 43 12 63 6f 6d 2e 63 6f 6d 70 61 6e 79 2e 6d 79 |C.com.company.my| - 00000010 75 73 65 72 92 0e 75 73 65 72 5f 66 75 6c 6c 5f |user..user_full_| - 00000020 6e 61 6d 65 11 66 61 6d 69 6c 79 50 68 6f 6e 65 |name.familyPhone| - 00000030 4e 75 6d 62 65 72 60 08 75 73 65 72 6e 61 6d 65 |Number`.username| - 00000040 0c 30 31 30 2d 31 32 33 34 35 36 37 38 |.010-12345678| +### Customizing Field Names with Tags + +The encoder converts Go field names to **lowerCamelCase** by default. Use the `hessian` tag to override: + +```go +type MyUser struct { + UserFullName string `hessian:"user_full_name"` // encoded as "user_full_name" + FamilyPhoneNumber string // encoded as "familyPhoneNumber" (default) +} + +func (MyUser) JavaClassName() string { + return "com.company.myuser" +} ``` -#### Decoding filed name +### Decoding Field Name Matching Rules + +When decoding, fields are matched in the following order: -Hessian decoder finds the correct target field though comparing all filed names of struct one by one until matching. +1. **Tag match** - matches the `hessian` tag value +2. **lowerCamelCase** - e.g., `mobilePhone` matches `MobilePhone` +3. **Exact case** - e.g., `MobilePhone` matches `MobilePhone` +4. **Lowercase** - e.g., `mobilephone` matches `MobilePhone` -The following example shows the order of the matching rules: ```go type MyUser struct { - MobilePhone string `hessian:"mobile-phone"` + MobilePhone string `hessian:"mobile-phone"` } +// Incoming field "mobile-phone" -> matched via tag (rule 1) +// Incoming field "mobilePhone" -> matched via lowerCamelCase (rule 2) +// Incoming field "MobilePhone" -> matched via exact case (rule 3) +// Incoming field "mobilephone" -> matched via lowercase (rule 4) +``` + +### Using a Custom Tag Identifier -// You must define the tag of struct for lookup filed form encoded binary bytes, in this case: -// 00000000 43 12 63 6f 6d 2e 63 6f 6d 70 61 6e 79 2e 6d 79 |C.com.company.my| -// 00000010 75 73 65 72 91 0c 6d 6f 62 69 6c 65 2d 70 68 6f |user..mobile-pho| -// 00000020 6e 65 60 0b 31 37 36 31 32 33 34 31 32 33 34 |ne`.17612341234| -// -// mobile-phone(tag lookup) => mobilePhone(lowerCameCase) => MobilePhone(SameCase) => mobilephone(lowercase) -// ^ will matched +Use `SetTagIdentifier` to read field names from a different struct tag (e.g., `json`): +```go +hessian.SetTagIdentifier("json") type MyUser struct { - MobilePhone string + UserFullName string `json:"user_full_name"` + FamilyPhoneNumber string } -// The following encoded binary bytes will be hit automatically: -// -// 00000000 43 12 63 6f 6d 2e 63 6f 6d 70 61 6e 79 2e 6d 79 |C.com.company.my| -// 00000010 75 73 65 72 91 0b 6d 6f 62 69 6c 65 50 68 6f 6e |user..mobilePhon| -// 00000020 65 60 0b 31 37 36 31 32 33 34 31 32 33 34 |e`.17612341234| -// -// mobile-phone(tag lookup) => mobilePhone(lowerCameCase) => MobilePhone(SameCase) => mobilephone(lowercase) -// ^ will matched -// -// 00000000 43 12 63 6f 6d 2e 63 6f 6d 70 61 6e 79 2e 6d 79 |C.com.company.my| -// 00000010 75 73 65 72 91 0b 4d 6f 62 69 6c 65 50 68 6f 6e |user..MobilePhon| -// 00000020 65 60 0b 31 37 36 31 32 33 34 31 32 33 34 |e`.17612341234| -// -// mobile-phone(tag lookup) => mobilePhone(lowerCameCase) => MobilePhone(SameCase) => mobilephone(lowercase) -// ^ will matched -// -// 00000000 43 12 63 6f 6d 2e 63 6f 6d 70 61 6e 79 2e 6d 79 |C.com.company.my| -// 00000010 75 73 65 72 91 0b 6d 6f 62 69 6c 65 70 68 6f 6e |user..mobilephon| -// 00000020 65 60 0b 31 37 36 31 32 33 34 31 32 33 34 |e`.17612341234| -// -// mobile-phone(tag lookup) => mobilePhone(lowerCameCase) => MobilePhone(SameCase) => mobilephone(lowercase) -// ^ will matched - +func (MyUser) JavaClassName() string { + return "com.company.myuser" +} ``` -#### Encoding param name - -When a Java method declares an argument as a parent class, it actually hope receives a subclass, -You can specify the encoding type of the parameter separately. +### Specifying Java Parameter Types (Inheritance) -##### java-server +When a Java method expects a parent class but you send a subclass, implement the `Param` interface: +**Java side:** ```java -public abstract class User { -} +public abstract class User {} public class MyUser extends User implements Serializable { - private String userFullName; - private String familyPhoneNumber; } public interface UserProvider { - String GetUser(User user); -} - -public class UserProviderImpl implements UserProvider { - public UserProviderImpl() { - } - - public String GetUser(User user) { - MyUser myUser=(MyUser)user; - return myUser.getUserFullName(); - } + String GetUser(User user); // accepts parent type } - ``` -##### go-client - +**Go side:** ```go type MyUser struct { - UserFullName string `hessian:"userFullName"` - FamilyPhoneNumber string // default convert to => familyPhoneNumber + UserFullName string `hessian:"userFullName"` + FamilyPhoneNumber string } func (m *MyUser) JavaClassName() string { return "com.company.MyUser" } +// JavaParamName tells the encoder to use the parent class name in the method signature func (m *MyUser) JavaParamName() string { return "com.company.User" } - -type UserProvider struct { - GetUser func(ctx context.Context, user *MyUser) (string, error) `dubbo:"GetUser"` -} ``` +### Working with Java Collections +Map a Java collection class (e.g., `HashSet`) to a Go struct: -#### Set method Alias +```go +type JavaHashSet struct { + value []interface{} +} -When the Go client calls the Java server, the first letter of the method is converted to lowercase by default,you can use the dubbo tag to set method alias. +func (j *JavaHashSet) Get() []interface{} { return j.value } +func (j *JavaHashSet) Set(v []interface{}) { j.value = v } +func (j *JavaHashSet) JavaClassName() string { return "java.util.HashSet" } -```go -type UserProvider struct { - GetUser func(ctx context.Context) (*User, error) `dubbo:"GetUser"` +func init() { + hessian.SetCollectionSerialize(&JavaHashSet{}) } ``` -#### hessian.SetTagIdentifier - -You can use `hessian.SetTagIdentifier` to customize tag-identifier of hessian, which takes effect to both encoder and decoder. +Without this registration, Java collections are decoded as `[]interface{}`. -Example: +### Working with Java Enums ```go -hessian.SetTagIdentifier("json") +type Color int32 -type MyUser struct { - UserFullName string `json:"user_full_name"` - FamilyPhoneNumber string // default convert to => familyPhoneNumber -} +const ( + RED Color = 0 + GREEN Color = 1 + BLUE Color = 2 +) -func (MyUser) JavaClassName() string { - return "com.company.myuser" +var colorNames = map[Color]string{ + RED: "RED", GREEN: "GREEN", BLUE: "BLUE", } - -user := &MyUser{ - UserFullName: "username", - FamilyPhoneNumber: "010-12345678", +var colorValues = map[string]Color{ + "RED": RED, "GREEN": GREEN, "BLUE": BLUE, } -e := hessian.NewEncoder() -err := e.Encode(user) -if err != nil { - panic(err) +func (c Color) JavaClassName() string { return "com.example.Color" } +func (c Color) String() string { return colorNames[c] } +func (c Color) EnumValue(s string) hessian.JavaEnum { + return hessian.JavaEnum(colorValues[s]) } -``` -The encoded bytes of the struct `MyUser` is as following: - -```text - 00000000 43 12 63 6f 6d 2e 63 6f 6d 70 61 6e 79 2e 6d 79 |C.com.company.my| - 00000010 75 73 65 72 92 0e 75 73 65 72 5f 66 75 6c 6c 5f |user..user_full_| - 00000020 6e 61 6d 65 11 66 61 6d 69 6c 79 50 68 6f 6e 65 |name.familyPhone| - 00000030 4e 75 6d 62 65 72 60 08 75 73 65 72 6e 61 6d 65 |Number`.username| - 00000040 0c 30 31 30 2d 31 32 33 34 35 36 37 38 |.010-12345678| +func init() { + hessian.RegisterJavaEnum(RED) +} ``` -#### Using Java collections +### Custom Serializer -By default, the output of Hessian Java impl of a Java collection like java.util.HashSet will be decoded as `[]interface{}` in `go-hessian2`. -To apply the one-to-one mapping relationship between certain Java collection class and your Go struct, examples are as follows: +Implement the `Serializer` interface for full control over encoding/decoding: ```go -//use HashSet as example -//define your struct, which should implements hessian.JavaCollectionObject -type JavaHashSet struct { - value []interface{} -} - -//get the inside slice value -func (j *JavaHashSet) Get() []interface{} { - return j.value -} +type MySerializer struct{} -//set the inside slice value -func (j *JavaHashSet) Set(v []interface{}) { - j.value = v +func (s *MySerializer) EncObject(encoder *hessian.Encoder, obj hessian.POJO) error { + // Custom encoding logic + return nil } -//should be the same as the class name of the Java collection -func (j *JavaHashSet) JavaClassName() string { - return "java.util.HashSet" +func (s *MySerializer) DecObject(decoder *hessian.Decoder, typ reflect.Type, cls *hessian.ClassInfo) (interface{}, error) { + // Custom decoding logic + return nil, nil } func init() { - //register your struct so that hessian can recognized it when encoding and decoding - SetCollectionSerialize(&JavaHashSet{}) + hessian.SetSerializer("com.example.MyClass", &MySerializer{}) } ``` +### Strict Mode +By default, unregistered objects are decoded as maps. Use strict mode to get errors instead: -## Notice for inheritance +```go +decoder := hessian.NewDecoder(data) +decoder.Strict = true // returns error for unregistered types -`go-hessian2` supports inheritance struct, but the following situations should be avoided. +// Or use the convenience constructor: +decoder = hessian.NewStrictDecoder(data) +``` -+ **Avoid fields with the same name in multiple parent struct** +### Reusing Encoder/Decoder (Object Pool) -The following struct `C` have inherited field `Name`(default from the first parent), -but it's confused in logic. +For high-performance scenarios, reuse encoder/decoder instances: ```go -type A struct { Name string } -type B struct { Name string } -type C struct { - A - B -} +// Encoder reuse +encoder := hessian.NewEncoder() +encoder.Encode(obj1) +data1 := encoder.Buffer() + +encoder.Clean() // or encoder.ReuseBufferClean() to keep the buffer +encoder.Encode(obj2) +data2 := encoder.Buffer() + +// Decoder reuse (poolable decoder) +decoder := hessian.NewCheapDecoderWithSkip(data1) +obj1, _ := decoder.Decode() + +decoder.Reset(data2) // reuse with new data +obj2, _ := decoder.Decode() ``` -+ **Avoid inheritance for a pointer of struct** +## Struct Inheritance -The following definition is valid for golang syntax, -but the parent will be nil when create a new Dog, like `dog := Dog{}`, -which will not happen in java inheritance, -and is also not supported by `go-hessian2`. +Go struct embedding is supported for modeling Java inheritance: ```go +type Animal struct { + Name string +} + +func (Animal) JavaClassName() string { return "com.example.Animal" } + type Dog struct { - *Animal + Animal // embedded parent struct + Breed string } -``` -## Strict Mode +func (Dog) JavaClassName() string { return "com.example.Dog" } +``` -Default, hessian2 will decode an object to map if it's not being registered. -If you don't want that, change the decoder to strict mode as following, -and it will return error when meeting unregistered object. +**Avoid these patterns:** -```go -e := hessian.NewDecoder(bytes) -e.Strict = true // set to strict mode, default is false +1. **Duplicate field names across parents** - ambiguous field resolution: + ```go + type A struct { Name string } + type B struct { Name string } + type C struct { A; B } // which Name? + ``` -// or -e := hessian.NewStrictDecoder(bytes) -``` +2. **Pointer embedding** - nil parent at initialization, not supported: + ```go + type Dog struct { + *Animal // will be nil in Dog{}, not supported + } + ``` ## Tools -### tools/gen-go-enum +### gen-go-enum + +A code generation tool for creating Go enum types compatible with Java enums. See [tools/gen-go-enum/README.md](tools/gen-go-enum/README.md) for details. + +## Reference -A tool for generate hessian2 java enum define golang code. Read more [details](tools/gen-go-enum/README.md). +- [Hessian 2.0 Serialization Protocol Spec](http://hessian.caucho.com/doc/hessian-serialization.html) +- [Apache Dubbo-Go](https://github.com/apache/dubbo-go) +- [GoDoc API Reference](https://godoc.org/github.com/apache/dubbo-go-hessian2)