hessian2 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- hessian2-0.1.0/Cargo.lock +137 -0
- hessian2-0.1.0/Cargo.toml +15 -0
- hessian2-0.1.0/LICENSE +18 -0
- hessian2-0.1.0/PKG-INFO +470 -0
- hessian2-0.1.0/README.md +438 -0
- hessian2-0.1.0/crates/hessian2-core/Cargo.toml +16 -0
- hessian2-0.1.0/crates/hessian2-core/LICENSE +18 -0
- hessian2-0.1.0/crates/hessian2-core/README.md +79 -0
- hessian2-0.1.0/crates/hessian2-core/src/error.rs +66 -0
- hessian2-0.1.0/crates/hessian2-core/src/lib.rs +262 -0
- hessian2-0.1.0/crates/hessian2-core/src/reader.rs +434 -0
- hessian2-0.1.0/crates/hessian2-core/src/text.rs +480 -0
- hessian2-0.1.0/crates/hessian2-core/src/writer.rs +313 -0
- hessian2-0.1.0/crates/hessian2-core/tests/mined.rs +133 -0
- hessian2-0.1.0/crates/hessian2-core/tests/protocol_review.rs +509 -0
- hessian2-0.1.0/crates/hessian2-core/tests/spec.rs +194 -0
- hessian2-0.1.0/crates/hessian2-python/Cargo.toml +16 -0
- hessian2-0.1.0/crates/hessian2-python/LICENSE +18 -0
- hessian2-0.1.0/crates/hessian2-python/src/address.rs +32 -0
- hessian2-0.1.0/crates/hessian2-python/src/builtins.rs +622 -0
- hessian2-0.1.0/crates/hessian2-python/src/codec.rs +585 -0
- hessian2-0.1.0/crates/hessian2-python/src/decode.rs +576 -0
- hessian2-0.1.0/crates/hessian2-python/src/encode.rs +527 -0
- hessian2-0.1.0/crates/hessian2-python/src/lib.rs +39 -0
- hessian2-0.1.0/crates/hessian2-python/src/object.rs +90 -0
- hessian2-0.1.0/crates/hessian2-python/src/time.rs +195 -0
- hessian2-0.1.0/crates/hessian2-python/tests/describe.py +101 -0
- hessian2-0.1.0/crates/hessian2-python/tests/fixtures/go.json +443 -0
- hessian2-0.1.0/crates/hessian2-python/tests/fixtures/java.json +160 -0
- hessian2-0.1.0/crates/hessian2-python/tests/test_api.py +934 -0
- hessian2-0.1.0/crates/hessian2-python/tests/test_free_threading.py +67 -0
- hessian2-0.1.0/crates/hessian2-python/tests/test_go_interop.py +76 -0
- hessian2-0.1.0/crates/hessian2-python/tests/test_java_interop.py +339 -0
- hessian2-0.1.0/crates/hessian2-python/tests/test_nested.py +419 -0
- hessian2-0.1.0/crates/hessian2-python/tests/test_properties.py +136 -0
- hessian2-0.1.0/crates/hessian2-python/tests/test_protocol_review.py +173 -0
- hessian2-0.1.0/crates/hessian2-python/tests/test_readme.py +41 -0
- hessian2-0.1.0/crates/hessian2-python/tests/test_review2.py +340 -0
- hessian2-0.1.0/crates/hessian2-python/tests/test_review2_java.py +167 -0
- hessian2-0.1.0/crates/hessian2-python/tests/test_security.py +482 -0
- hessian2-0.1.0/crates/hessian2-python/tests/test_stdlib.py +220 -0
- hessian2-0.1.0/pyproject.toml +61 -0
- hessian2-0.1.0/python/hessian2/__init__.py +246 -0
- hessian2-0.1.0/python/hessian2/_core.pyi +68 -0
- hessian2-0.1.0/python/hessian2/py.typed +0 -0
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
# This file is automatically @generated by Cargo.
|
|
2
|
+
# It is not intended for manual editing.
|
|
3
|
+
version = 4
|
|
4
|
+
|
|
5
|
+
[[package]]
|
|
6
|
+
name = "heck"
|
|
7
|
+
version = "0.5.0"
|
|
8
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
9
|
+
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
|
10
|
+
|
|
11
|
+
[[package]]
|
|
12
|
+
name = "hessian2-core"
|
|
13
|
+
version = "0.1.0"
|
|
14
|
+
|
|
15
|
+
[[package]]
|
|
16
|
+
name = "hessian2-python"
|
|
17
|
+
version = "0.1.0"
|
|
18
|
+
dependencies = [
|
|
19
|
+
"hessian2-core",
|
|
20
|
+
"pyo3",
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
[[package]]
|
|
24
|
+
name = "libc"
|
|
25
|
+
version = "0.2.191"
|
|
26
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
27
|
+
checksum = "65cd87c9a6d60536580b316ad985726104730a5151c99845f2490428ef91240a"
|
|
28
|
+
|
|
29
|
+
[[package]]
|
|
30
|
+
name = "once_cell"
|
|
31
|
+
version = "1.21.4"
|
|
32
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
33
|
+
checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
|
|
34
|
+
|
|
35
|
+
[[package]]
|
|
36
|
+
name = "portable-atomic"
|
|
37
|
+
version = "1.15.0"
|
|
38
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
39
|
+
checksum = "05c8b63e8d9609db387f0324918f81d68fe27748f084ef092fb35954d0539a85"
|
|
40
|
+
|
|
41
|
+
[[package]]
|
|
42
|
+
name = "proc-macro2"
|
|
43
|
+
version = "1.0.107"
|
|
44
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
45
|
+
checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9"
|
|
46
|
+
dependencies = [
|
|
47
|
+
"unicode-ident",
|
|
48
|
+
]
|
|
49
|
+
|
|
50
|
+
[[package]]
|
|
51
|
+
name = "pyo3"
|
|
52
|
+
version = "0.30.1"
|
|
53
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
54
|
+
checksum = "229ca13b8a15bb0d1487b81ab975bfcd97513416499a8312011b6036e42e0de4"
|
|
55
|
+
dependencies = [
|
|
56
|
+
"libc",
|
|
57
|
+
"once_cell",
|
|
58
|
+
"portable-atomic",
|
|
59
|
+
"pyo3-build-config",
|
|
60
|
+
"pyo3-ffi",
|
|
61
|
+
"pyo3-macros",
|
|
62
|
+
]
|
|
63
|
+
|
|
64
|
+
[[package]]
|
|
65
|
+
name = "pyo3-build-config"
|
|
66
|
+
version = "0.30.1"
|
|
67
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
68
|
+
checksum = "70130e2b6dd1ee589a8788026a429561ca5c40fa7a2fa7914c2282eddae24700"
|
|
69
|
+
dependencies = [
|
|
70
|
+
"target-lexicon",
|
|
71
|
+
]
|
|
72
|
+
|
|
73
|
+
[[package]]
|
|
74
|
+
name = "pyo3-ffi"
|
|
75
|
+
version = "0.30.1"
|
|
76
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
77
|
+
checksum = "5e7ff73c555c286d212d4ce7e5fbda845a973c4f61bd5f160f5f2c3c2a04d1c4"
|
|
78
|
+
dependencies = [
|
|
79
|
+
"libc",
|
|
80
|
+
"pyo3-build-config",
|
|
81
|
+
]
|
|
82
|
+
|
|
83
|
+
[[package]]
|
|
84
|
+
name = "pyo3-macros"
|
|
85
|
+
version = "0.30.1"
|
|
86
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
87
|
+
checksum = "c46d2e5862c7bb8207e3b639f713dda3c499933444784154c11d013c5a691b60"
|
|
88
|
+
dependencies = [
|
|
89
|
+
"proc-macro2",
|
|
90
|
+
"pyo3-macros-backend",
|
|
91
|
+
"quote",
|
|
92
|
+
"syn",
|
|
93
|
+
]
|
|
94
|
+
|
|
95
|
+
[[package]]
|
|
96
|
+
name = "pyo3-macros-backend"
|
|
97
|
+
version = "0.30.1"
|
|
98
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
99
|
+
checksum = "38b89f066f2ae72a2aef52d1537ca2fcd104440ebfadee06e97aa90738a25b9b"
|
|
100
|
+
dependencies = [
|
|
101
|
+
"heck",
|
|
102
|
+
"proc-macro2",
|
|
103
|
+
"quote",
|
|
104
|
+
"syn",
|
|
105
|
+
]
|
|
106
|
+
|
|
107
|
+
[[package]]
|
|
108
|
+
name = "quote"
|
|
109
|
+
version = "1.0.47"
|
|
110
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
111
|
+
checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001"
|
|
112
|
+
dependencies = [
|
|
113
|
+
"proc-macro2",
|
|
114
|
+
]
|
|
115
|
+
|
|
116
|
+
[[package]]
|
|
117
|
+
name = "syn"
|
|
118
|
+
version = "3.0.10"
|
|
119
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
120
|
+
checksum = "4ae9ccbea3a7b0b09443036490c8f34a8b1b760fd345e5c0dae0aac718faea79"
|
|
121
|
+
dependencies = [
|
|
122
|
+
"proc-macro2",
|
|
123
|
+
"quote",
|
|
124
|
+
"unicode-ident",
|
|
125
|
+
]
|
|
126
|
+
|
|
127
|
+
[[package]]
|
|
128
|
+
name = "target-lexicon"
|
|
129
|
+
version = "0.13.5"
|
|
130
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
131
|
+
checksum = "adb6935a6f5c20170eeceb1a3835a49e12e19d792f6dd344ccc76a985ca5a6ca"
|
|
132
|
+
|
|
133
|
+
[[package]]
|
|
134
|
+
name = "unicode-ident"
|
|
135
|
+
version = "1.0.27"
|
|
136
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
137
|
+
checksum = "a2c754d6c33795a1c324727428e5a7dedb5b06195f9890bdbcba760d3e246563"
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
[workspace]
|
|
2
|
+
members = ["crates/hessian2-core", "crates/hessian2-python"]
|
|
3
|
+
exclude = ["fuzz"]
|
|
4
|
+
resolver = "3"
|
|
5
|
+
|
|
6
|
+
[workspace.package]
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
edition = "2024"
|
|
9
|
+
rust-version = "1.88"
|
|
10
|
+
license = "MIT"
|
|
11
|
+
|
|
12
|
+
# Whole-program optimization for the published library and extension.
|
|
13
|
+
[profile.release]
|
|
14
|
+
lto = "fat"
|
|
15
|
+
codegen-units = 1
|
hessian2-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 zaki and other contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
|
|
6
|
+
associated documentation files (the "Software"), to deal in the Software without restriction, including
|
|
7
|
+
without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
8
|
+
copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the
|
|
9
|
+
following conditions:
|
|
10
|
+
|
|
11
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial
|
|
12
|
+
portions of the Software.
|
|
13
|
+
|
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
|
|
15
|
+
LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO
|
|
16
|
+
EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
|
17
|
+
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
|
18
|
+
USE OR OTHER DEALINGS IN THE SOFTWARE.
|
hessian2-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,470 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: hessian2
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Classifier: Development Status :: 3 - Alpha
|
|
5
|
+
Classifier: Intended Audience :: Developers
|
|
6
|
+
Classifier: Programming Language :: Python :: 3
|
|
7
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
8
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.15
|
|
13
|
+
Classifier: Programming Language :: Python :: Free Threading :: 2 - Beta
|
|
14
|
+
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
15
|
+
Classifier: Programming Language :: Rust
|
|
16
|
+
Classifier: Typing :: Typed
|
|
17
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
18
|
+
Classifier: Topic :: System :: Distributed Computing
|
|
19
|
+
License-File: LICENSE
|
|
20
|
+
Summary: Fast Hessian 2.0 serialization for Python, with a Rust core.
|
|
21
|
+
Keywords: hessian,hessian2,serialization,rust
|
|
22
|
+
License-Expression: MIT
|
|
23
|
+
Requires-Python: >=3.11
|
|
24
|
+
Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
|
|
25
|
+
Project-URL: Changelog, https://github.com/cnzakii/hessian2/blob/main/CHANGELOG.md
|
|
26
|
+
Project-URL: Documentation, https://github.com/cnzakii/hessian2#readme
|
|
27
|
+
Project-URL: Homepage, https://github.com/cnzakii/hessian2
|
|
28
|
+
Project-URL: Issues, https://github.com/cnzakii/hessian2/issues
|
|
29
|
+
Project-URL: Repository, https://github.com/cnzakii/hessian2
|
|
30
|
+
Project-URL: Security, https://github.com/cnzakii/hessian2/security/policy
|
|
31
|
+
|
|
32
|
+
# hessian2
|
|
33
|
+
|
|
34
|
+
[](https://github.com/cnzakii/hessian2/actions/workflows/ci.yml)
|
|
35
|
+
[](https://github.com/cnzakii/hessian2/actions/workflows/coverage.yml)
|
|
36
|
+
[](https://codecov.io/gh/cnzakii/hessian2)
|
|
37
|
+

|
|
38
|
+

|
|
39
|
+
[](LICENSE)
|
|
40
|
+
|
|
41
|
+
Fast [Hessian 2.0](http://hessian.caucho.com/doc/hessian-serialization.html)
|
|
42
|
+
serialization for Python, powered by Rust. Exchange Python objects with
|
|
43
|
+
Java and Go applications without converting nested data to dictionaries first.
|
|
44
|
+
|
|
45
|
+
- **Fast.** Encode large strings at **1.7–12.3×** and decode them at
|
|
46
|
+
**1.5–18.0×** the speed of Java Caucho Hessian in the
|
|
47
|
+
[text benchmarks](#performance).
|
|
48
|
+
- **Python objects.** Round-trip dataclasses, attrs classes, named tuples,
|
|
49
|
+
and enums. Use a Converter for Pydantic models and other custom types.
|
|
50
|
+
- **Shared references.** Preserve object identity across nested objects and
|
|
51
|
+
collections, including cycles in lists, dictionaries, and dataclass/attrs
|
|
52
|
+
instances.
|
|
53
|
+
- **Built-in Java mappings.** Send decimals, UUIDs, dates, durations, and
|
|
54
|
+
large integers without writing conversion callbacks.
|
|
55
|
+
|
|
56
|
+
> Alpha: APIs may change before a stable release.
|
|
57
|
+
|
|
58
|
+
## Installation
|
|
59
|
+
|
|
60
|
+
Requires CPython 3.11+ and has no runtime dependencies. Install with pip:
|
|
61
|
+
|
|
62
|
+
```console
|
|
63
|
+
python -m pip install hessian2
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Or add it to a uv project:
|
|
67
|
+
|
|
68
|
+
```console
|
|
69
|
+
uv add hessian2
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
For source builds and development setup, see
|
|
73
|
+
[CONTRIBUTING.md](CONTRIBUTING.md).
|
|
74
|
+
|
|
75
|
+
## Quick start
|
|
76
|
+
|
|
77
|
+
For plain values, use the module-level functions:
|
|
78
|
+
|
|
79
|
+
```python
|
|
80
|
+
import hessian2
|
|
81
|
+
|
|
82
|
+
payload = hessian2.dumps({"name": "alice", "tags": ["customer"]})
|
|
83
|
+
received = hessian2.loads(payload)
|
|
84
|
+
print(received["tags"]) # ['customer']
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### Nested objects, references, and built-in types
|
|
88
|
+
|
|
89
|
+
Register the Java class names your peer uses. Here an order contains a
|
|
90
|
+
customer and an address, and both address fields point to **the same
|
|
91
|
+
Address instance**. Decimal, UUID, and an aware datetime use the built-in
|
|
92
|
+
Java mappings without registration.
|
|
93
|
+
|
|
94
|
+
```python
|
|
95
|
+
from dataclasses import dataclass
|
|
96
|
+
from datetime import UTC, datetime
|
|
97
|
+
from decimal import Decimal
|
|
98
|
+
from uuid import UUID
|
|
99
|
+
|
|
100
|
+
import hessian2
|
|
101
|
+
|
|
102
|
+
codec = hessian2.Codec(field_naming="camelCase")
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
@codec.register("com.example.Address")
|
|
106
|
+
@dataclass
|
|
107
|
+
class Address:
|
|
108
|
+
city: str
|
|
109
|
+
postal_code: str
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
@codec.register("com.example.Customer")
|
|
113
|
+
@dataclass
|
|
114
|
+
class Customer:
|
|
115
|
+
user_name: str
|
|
116
|
+
address: Address
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
@codec.register("com.example.Order")
|
|
120
|
+
@dataclass
|
|
121
|
+
class Order:
|
|
122
|
+
order_id: UUID
|
|
123
|
+
customer: Customer
|
|
124
|
+
delivery_address: Address
|
|
125
|
+
total: Decimal
|
|
126
|
+
created_at: datetime
|
|
127
|
+
tags: list[str]
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
address = Address(city="Hangzhou", postal_code="310000")
|
|
131
|
+
order = Order(
|
|
132
|
+
order_id=UUID("123e4567-e89b-12d3-a456-426614174000"),
|
|
133
|
+
customer=Customer(user_name="alice", address=address),
|
|
134
|
+
delivery_address=address,
|
|
135
|
+
total=Decimal("19.90"),
|
|
136
|
+
created_at=datetime(2026, 1, 1, 12, 0, tzinfo=UTC),
|
|
137
|
+
tags=["paid", "delivery"],
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
# Python names become Java fields: orderId, deliveryAddress, postalCode, ...
|
|
141
|
+
# The address is sent once; the second occurrence is a Hessian reference.
|
|
142
|
+
payload = codec.dumps(order)
|
|
143
|
+
received = codec.loads(payload)
|
|
144
|
+
|
|
145
|
+
print(received.customer.user_name) # alice
|
|
146
|
+
print(received.delivery_address.city) # Hangzhou
|
|
147
|
+
print(received.customer.address is received.delivery_address) # True
|
|
148
|
+
print(received.total, type(received.total).__name__) # 19.90 Decimal
|
|
149
|
+
print(received.order_id, type(received.order_id).__name__)
|
|
150
|
+
# 123e4567-e89b-12d3-a456-426614174000 UUID
|
|
151
|
+
print(received.created_at.isoformat()) # 2026-01-01T12:00:00+00:00
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
Registration applies throughout the object graph: nested values can be
|
|
155
|
+
registered objects, lists, maps, and standard-library values. Shared
|
|
156
|
+
references are preserved. Lists, dictionaries, dataclass/attrs instances,
|
|
157
|
+
and `HessianObject` values also support cycles. Registered named tuples
|
|
158
|
+
reject cycles during encoding; Converter values cannot be referenced back
|
|
159
|
+
to while their fields are being decoded.
|
|
160
|
+
|
|
161
|
+
### Multiple values in one stream
|
|
162
|
+
|
|
163
|
+
`loads` accepts bytes-like input and requires exactly one value. Use
|
|
164
|
+
`dumps_many` / `loads_many` for several top-level values sharing a stream's
|
|
165
|
+
class definitions and references:
|
|
166
|
+
|
|
167
|
+
```python
|
|
168
|
+
import hessian2
|
|
169
|
+
|
|
170
|
+
shared = {"status": "ready"}
|
|
171
|
+
stream = hessian2.dumps_many(["header", shared, shared])
|
|
172
|
+
header, first, second = hessian2.loads_many(stream)
|
|
173
|
+
print(header, first["status"], first is second) # header ready True
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
## Performance
|
|
177
|
+
|
|
178
|
+
The following measurements compare hessian2 with **Caucho Hessian 4.0.66**,
|
|
179
|
+
the Java reference implementation, on four roughly 1 MB text samples. Encoding
|
|
180
|
+
is **1.7–12.3×** and decoding **1.5–18.0×** as fast in these samples.
|
|
181
|
+
|
|
182
|
+
### Encoding
|
|
183
|
+
|
|
184
|
+
Times are per complete encoding call, in microseconds (µs); lower is better.
|
|
185
|
+
Speedup is Caucho's time divided by hessian2's time.
|
|
186
|
+
|
|
187
|
+
| Text | hessian2 | Java Caucho | Speedup |
|
|
188
|
+
|---|---:|---:|---:|
|
|
189
|
+
| ASCII | 71 µs | 869 µs | **12.3×** |
|
|
190
|
+
| Chinese | 162 µs | 476 µs | **2.9×** |
|
|
191
|
+
| Mixed | 390 µs | 664 µs | **1.7×** |
|
|
192
|
+
| Emoji | 435 µs | 730 µs | **1.7×** |
|
|
193
|
+
|
|
194
|
+
### Decoding
|
|
195
|
+
|
|
196
|
+
Times are per complete decoding call. Both implementations read the
|
|
197
|
+
**same Hessian bytes**; speedup uses the same calculation as above.
|
|
198
|
+
|
|
199
|
+
| Text | hessian2 | Java Caucho | Speedup |
|
|
200
|
+
|---|---:|---:|---:|
|
|
201
|
+
| ASCII | 110 µs | 1,990 µs | **18.0×** |
|
|
202
|
+
| Chinese | 552 µs | 1,452 µs | **2.6×** |
|
|
203
|
+
| Mixed | 1,089 µs | 1,611 µs | **1.5×** |
|
|
204
|
+
| Emoji | 900 µs | 1,761 µs | **2.0×** |
|
|
205
|
+
|
|
206
|
+
For small messages and object workloads, benchmark representative application
|
|
207
|
+
data; the text results above do not predict their performance.
|
|
208
|
+
|
|
209
|
+
<details>
|
|
210
|
+
<summary>hessian-lite and Go results</summary>
|
|
211
|
+
|
|
212
|
+
Against Java hessian-lite, encoding speed is **1.6–12.2×** and decoding
|
|
213
|
+
speed **1.5–17.7×**. Against Go Hessian, encoding speed is **3.2–31.5×**;
|
|
214
|
+
decoding is workload-dependent: **Go is faster on mixed text**, where
|
|
215
|
+
hessian2 takes 26% more time.
|
|
216
|
+
|
|
217
|
+
All values below are microseconds per complete call; lower is better.
|
|
218
|
+
The hessian2 timings are in the tables above.
|
|
219
|
+
|
|
220
|
+
| Text | hessian-lite encode | hessian-lite decode | Go encode | Go decode |
|
|
221
|
+
|---|---:|---:|---:|---:|
|
|
222
|
+
| ASCII | 867 | 1,946 | 2,228 | 520 |
|
|
223
|
+
| Chinese | 495 | 1,258 | 1,773 | 669 |
|
|
224
|
+
| Mixed | 635 | 1,650 | 1,915 | 864 |
|
|
225
|
+
| Emoji | 723 | 1,873 | 1,397 | 22,651 |
|
|
226
|
+
|
|
227
|
+
Go's decoder is sensitive to chunk layout. On its own output, mixed-text
|
|
228
|
+
decoding takes 649 µs and emoji 4,081 µs, rather than 864 µs and 22,651 µs
|
|
229
|
+
on the shared input. The large emoji gap therefore depends heavily on
|
|
230
|
+
which writer produced the bytes.
|
|
231
|
+
|
|
232
|
+
</details>
|
|
233
|
+
|
|
234
|
+
<details>
|
|
235
|
+
<summary>Benchmark setup and reproduction</summary>
|
|
236
|
+
|
|
237
|
+
Apple M4, macOS, CPython 3.13.12, OpenJDK 17.0.20, Go 1.26.4.
|
|
238
|
+
Peers: Caucho Hessian 4.0.66, hessian-lite 4.0.5, dubbo-go-hessian2 1.13.3.
|
|
239
|
+
Release builds, warmed inputs, median of five rounds of 20 calls.
|
|
240
|
+
|
|
241
|
+
Samples contain roughly 0.9–1 MB of UTF-8: ASCII, Chinese, sparse emoji
|
|
242
|
+
mixed with English and Chinese, and emoji only. Every decoder reads the
|
|
243
|
+
same Hessian bytes. Both Java writers also produce exactly those bytes
|
|
244
|
+
for these samples. Java encoding includes copying the complete output
|
|
245
|
+
byte array. Multipliers use unrounded timings; displayed times are rounded.
|
|
246
|
+
|
|
247
|
+
With the release extension installed, JDK 17+ and Go available:
|
|
248
|
+
|
|
249
|
+
```console
|
|
250
|
+
make interop-java
|
|
251
|
+
uv run --locked --group bench python benchmarks/bench_hessian.py --iterations 20 --rounds 5
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
Results are written to `target/hessian-benchmark/results.json`.
|
|
255
|
+
For short strings, ordinary objects, wide classes, big integers, and repeated
|
|
256
|
+
references, use `benchmarks/bench_paths.py` for regression measurements.
|
|
257
|
+
|
|
258
|
+
</details>
|
|
259
|
+
|
|
260
|
+
<details>
|
|
261
|
+
<summary>Comparing with other Python formats</summary>
|
|
262
|
+
|
|
263
|
+
For Python-only applications, `benchmarks/bench.py` compares hessian2 with
|
|
264
|
+
pickle, MessagePack, and JSON on the same source values. The formats preserve
|
|
265
|
+
different types and references, so choose one that fits your application.
|
|
266
|
+
|
|
267
|
+
```console
|
|
268
|
+
uv run --locked --group bench python benchmarks/bench.py --fast -o target/formats.json
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
The `users` workload contains dataclasses. hessian2 and pickle reconstruct
|
|
272
|
+
those classes; msgpack calls `dataclasses.asdict` during encoding, orjson
|
|
273
|
+
encodes dataclasses natively, and both return dictionaries. Decimal/UUID
|
|
274
|
+
conversion and binary representation also differ. Include any conversions
|
|
275
|
+
your application needs when interpreting the results.
|
|
276
|
+
|
|
277
|
+
</details>
|
|
278
|
+
|
|
279
|
+
## Registered classes
|
|
280
|
+
|
|
281
|
+
| Register | Object representation |
|
|
282
|
+
|---|---|
|
|
283
|
+
| dataclass, attrs class | One field per declared attribute |
|
|
284
|
+
| `typing.NamedTuple`, `collections.namedtuple` | One field per named item |
|
|
285
|
+
| `enum.Enum` | A `name` field, as Java writes an enum |
|
|
286
|
+
| `Converter` for any other type | The field dictionary returned by `to_fields` |
|
|
287
|
+
|
|
288
|
+
Fields match by name, not position. Missing fields take their defaults;
|
|
289
|
+
missing required fields raise `DecodeError`. Unknown fields are consumed and
|
|
290
|
+
ignored. Field naming must not map multiple Python attributes to the same
|
|
291
|
+
wire name: `user_name` and `userName` conflict under `"camelCase"`.
|
|
292
|
+
|
|
293
|
+
Dataclass and attrs decoding bypasses `__init__`, fills fields, then invokes
|
|
294
|
+
`__post_init__` or `__attrs_post_init__`. Dataclass InitVars use their defaults;
|
|
295
|
+
required InitVars needed by post-init are rejected at registration. attrs
|
|
296
|
+
validators and converters do not run. Annotations are not validated. Use a
|
|
297
|
+
validation hook or a Converter when validation is required.
|
|
298
|
+
|
|
299
|
+
### Options and registration conflicts
|
|
300
|
+
|
|
301
|
+
```python
|
|
302
|
+
import hessian2
|
|
303
|
+
|
|
304
|
+
codec = hessian2.Codec(
|
|
305
|
+
field_naming="camelCase", # None keeps Python field names unchanged
|
|
306
|
+
unknown_class="raise", # "hessian_object" or "dict" for unknown names
|
|
307
|
+
max_depth=256, # Maximum nesting of objects, lists, and maps
|
|
308
|
+
)
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
Duplicate registration of the same type under the same name is allowed.
|
|
312
|
+
A name assigned to a different type, or a type assigned to a different name,
|
|
313
|
+
raises `ValueError` unless you explicitly pass `replace=True`:
|
|
314
|
+
|
|
315
|
+
```python
|
|
316
|
+
from dataclasses import dataclass
|
|
317
|
+
|
|
318
|
+
import hessian2
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
@dataclass
|
|
322
|
+
class OldPoint:
|
|
323
|
+
x: int
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
@dataclass
|
|
327
|
+
class NewPoint:
|
|
328
|
+
x: int
|
|
329
|
+
y: int = 0
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
codec = hessian2.Codec()
|
|
333
|
+
codec.register("com.example.Point", OldPoint)
|
|
334
|
+
# Explicit replacement; without replace=True this raises ValueError.
|
|
335
|
+
codec.register("com.example.Point", NewPoint, replace=True)
|
|
336
|
+
point = codec.loads(codec.dumps(NewPoint(3, 4)))
|
|
337
|
+
print(point) # NewPoint(x=3, y=4)
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
`replace=True` can also override built-in JDK mappings. Moving a type to a
|
|
341
|
+
new encoding name keeps its old name as a decode alias. There is no global
|
|
342
|
+
registry; the module-level functions use only the built-in mappings.
|
|
343
|
+
|
|
344
|
+
### Pydantic and other types
|
|
345
|
+
|
|
346
|
+
A Converter adapts an arbitrary type through a field dictionary. Pydantic
|
|
347
|
+
v2 models can validate incoming fields with `model_validate`:
|
|
348
|
+
|
|
349
|
+
```python
|
|
350
|
+
from pydantic import BaseModel
|
|
351
|
+
|
|
352
|
+
import hessian2
|
|
353
|
+
|
|
354
|
+
|
|
355
|
+
class Order(BaseModel):
|
|
356
|
+
order_id: int
|
|
357
|
+
amount: float
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
codec = hessian2.Codec()
|
|
361
|
+
codec.register(
|
|
362
|
+
"com.example.Order",
|
|
363
|
+
hessian2.Converter(
|
|
364
|
+
Order,
|
|
365
|
+
to_fields=Order.model_dump,
|
|
366
|
+
from_fields=Order.model_validate,
|
|
367
|
+
),
|
|
368
|
+
)
|
|
369
|
+
order = Order(order_id=1, amount=9.5)
|
|
370
|
+
received = codec.loads(codec.dumps(order))
|
|
371
|
+
print(received.order_id, received.amount) # 1 9.5
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
Converter keys are sent unchanged; `field_naming` does not rename them.
|
|
375
|
+
`to_fields` runs once per object per stream. Further occurrences write
|
|
376
|
+
references to its first representation, not repeated callback calls.
|
|
377
|
+
Pydantic validation and coercion follow the model configuration; use
|
|
378
|
+
`model_validate(fields, strict=True)` in your callback for strict validation.
|
|
379
|
+
Plain classes and BaseModel classes are not automatically registered.
|
|
380
|
+
|
|
381
|
+
## Objects without Python classes
|
|
382
|
+
|
|
383
|
+
`HessianObject` sends a class name and fields without a corresponding Python
|
|
384
|
+
type. Its fields can themselves hold objects, collections, and references:
|
|
385
|
+
|
|
386
|
+
```python
|
|
387
|
+
import hessian2
|
|
388
|
+
from hessian2 import HessianObject
|
|
389
|
+
|
|
390
|
+
address = HessianObject("com.example.Address", {"city": "Hangzhou"})
|
|
391
|
+
user = HessianObject(
|
|
392
|
+
"com.example.User",
|
|
393
|
+
{"name": "alice", "home": address, "addresses": [address]},
|
|
394
|
+
)
|
|
395
|
+
|
|
396
|
+
codec = hessian2.Codec(unknown_class="hessian_object")
|
|
397
|
+
received = codec.loads(codec.dumps(user))
|
|
398
|
+
print(received.class_name) # com.example.User
|
|
399
|
+
print(received.fields["home"].fields["city"]) # Hangzhou
|
|
400
|
+
print(received.fields["home"] is received.fields["addresses"][0]) # True
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
For an unregistered class, `unknown_class="raise"` raises `DecodeError`,
|
|
404
|
+
`"hessian_object"` keeps its class name and fields, and `"dict"` returns only
|
|
405
|
+
its fields. The setting also applies to nested unknown objects.
|
|
406
|
+
|
|
407
|
+
## Type mappings
|
|
408
|
+
|
|
409
|
+
| Python | Hessian / Java |
|
|
410
|
+
|---|---|
|
|
411
|
+
| `None`, `bool`, `float`, `str`, `bytes` | `null`, boolean, double, String, byte array |
|
|
412
|
+
| `int` | int within 32 bits, long within 64 bits, otherwise BigInteger |
|
|
413
|
+
| `list`, unregistered `tuple` | Untyped list; decoded as `list` |
|
|
414
|
+
| `set`, `frozenset` | HashSet typed list; decoded as `list` |
|
|
415
|
+
| `dict` | Untyped map; decoded as `dict` |
|
|
416
|
+
| aware `datetime` | java.util.Date; decoded in UTC, millisecond precision |
|
|
417
|
+
| naive `datetime`, `date`, naive `time`, `timedelta` | LocalDateTime, LocalDate, LocalTime, Duration |
|
|
418
|
+
| `Decimal`, `UUID` | BigDecimal, UUID |
|
|
419
|
+
|
|
420
|
+
Typed Java lists and arrays decode as Python lists; typed maps decode as
|
|
421
|
+
dicts. Instant, OffsetDateTime, and ZonedDateTime decode as aware datetimes;
|
|
422
|
+
ZonedDateTime keeps its zone when `zoneinfo` knows it. Java nanoseconds are
|
|
423
|
+
truncated to Python microseconds. An aware `time` has no default Java mapping
|
|
424
|
+
and raises `EncodeError`.
|
|
425
|
+
|
|
426
|
+
## Compatibility
|
|
427
|
+
|
|
428
|
+
Tested in both directions with Caucho Hessian 4.0.66, hessian-lite 4.0.5,
|
|
429
|
+
and dubbo-go-hessian2 1.13.3. Java implementations determine the forms this
|
|
430
|
+
writer emits; individual type mappings still differ between peers.
|
|
431
|
+
|
|
432
|
+
- Java can read an int encoding into a long field. In an `Object` or generic
|
|
433
|
+
collection, however, a small Python integer arrives as `Integer`, not `Long`.
|
|
434
|
+
- Caucho sends `0.1f` as its exact double value (`0.10000000149011612`);
|
|
435
|
+
hessian-lite sends its shortest decimal (`0.1`). Both incoming values are
|
|
436
|
+
preserved as sent. Java's writer also drops negative zero's sign.
|
|
437
|
+
- `java.time` uses hessian-lite's handle forms. Caucho's default reflection
|
|
438
|
+
serializer cannot write these on JDK 17 without module access. The compact
|
|
439
|
+
java.time mode is not supported.
|
|
440
|
+
- Java sends supplementary characters as CESU-8 surrogate pairs. Both paired
|
|
441
|
+
and unpaired surrogates are accepted. Four-byte UTF-8 is also accepted on
|
|
442
|
+
read, though these Java peers do not write it.
|
|
443
|
+
- Go's compact millisecond-double decoder has a known rounding difference
|
|
444
|
+
from Java for some values; the interop tests record this explicitly.
|
|
445
|
+
|
|
446
|
+
## Safety
|
|
447
|
+
|
|
448
|
+
Unknown class names never trigger imports. Registration is a trust decision:
|
|
449
|
+
descriptors, default factories, post-init hooks, Converter callbacks, and map
|
|
450
|
+
key methods can run Python code. Field annotations do not validate input.
|
|
451
|
+
|
|
452
|
+
`DecodeError` subclasses `ValueError`; `EncodeError` subclasses `TypeError`.
|
|
453
|
+
Excess encoding depth raises `RecursionError`. Lengths and references are
|
|
454
|
+
checked, but `max_depth` does not cap total memory or CPU use. Limit incoming
|
|
455
|
+
message size in the application. See [SECURITY.md](SECURITY.md).
|
|
456
|
+
|
|
457
|
+
## Development and community
|
|
458
|
+
|
|
459
|
+
- [Contributing and release guide](CONTRIBUTING.md)
|
|
460
|
+
- [Code of Conduct](CODE_OF_CONDUCT.md)
|
|
461
|
+
- [Security policy](SECURITY.md)
|
|
462
|
+
- [Changelog](CHANGELOG.md)
|
|
463
|
+
|
|
464
|
+
[`hessian2-core`](crates/hessian2-core/README.md) provides the Rust reader
|
|
465
|
+
and writer, with a separate README and API. Both crates forbid unsafe code.
|
|
466
|
+
|
|
467
|
+
## License
|
|
468
|
+
|
|
469
|
+
MIT
|
|
470
|
+
|