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.
Files changed (45) hide show
  1. hessian2-0.1.0/Cargo.lock +137 -0
  2. hessian2-0.1.0/Cargo.toml +15 -0
  3. hessian2-0.1.0/LICENSE +18 -0
  4. hessian2-0.1.0/PKG-INFO +470 -0
  5. hessian2-0.1.0/README.md +438 -0
  6. hessian2-0.1.0/crates/hessian2-core/Cargo.toml +16 -0
  7. hessian2-0.1.0/crates/hessian2-core/LICENSE +18 -0
  8. hessian2-0.1.0/crates/hessian2-core/README.md +79 -0
  9. hessian2-0.1.0/crates/hessian2-core/src/error.rs +66 -0
  10. hessian2-0.1.0/crates/hessian2-core/src/lib.rs +262 -0
  11. hessian2-0.1.0/crates/hessian2-core/src/reader.rs +434 -0
  12. hessian2-0.1.0/crates/hessian2-core/src/text.rs +480 -0
  13. hessian2-0.1.0/crates/hessian2-core/src/writer.rs +313 -0
  14. hessian2-0.1.0/crates/hessian2-core/tests/mined.rs +133 -0
  15. hessian2-0.1.0/crates/hessian2-core/tests/protocol_review.rs +509 -0
  16. hessian2-0.1.0/crates/hessian2-core/tests/spec.rs +194 -0
  17. hessian2-0.1.0/crates/hessian2-python/Cargo.toml +16 -0
  18. hessian2-0.1.0/crates/hessian2-python/LICENSE +18 -0
  19. hessian2-0.1.0/crates/hessian2-python/src/address.rs +32 -0
  20. hessian2-0.1.0/crates/hessian2-python/src/builtins.rs +622 -0
  21. hessian2-0.1.0/crates/hessian2-python/src/codec.rs +585 -0
  22. hessian2-0.1.0/crates/hessian2-python/src/decode.rs +576 -0
  23. hessian2-0.1.0/crates/hessian2-python/src/encode.rs +527 -0
  24. hessian2-0.1.0/crates/hessian2-python/src/lib.rs +39 -0
  25. hessian2-0.1.0/crates/hessian2-python/src/object.rs +90 -0
  26. hessian2-0.1.0/crates/hessian2-python/src/time.rs +195 -0
  27. hessian2-0.1.0/crates/hessian2-python/tests/describe.py +101 -0
  28. hessian2-0.1.0/crates/hessian2-python/tests/fixtures/go.json +443 -0
  29. hessian2-0.1.0/crates/hessian2-python/tests/fixtures/java.json +160 -0
  30. hessian2-0.1.0/crates/hessian2-python/tests/test_api.py +934 -0
  31. hessian2-0.1.0/crates/hessian2-python/tests/test_free_threading.py +67 -0
  32. hessian2-0.1.0/crates/hessian2-python/tests/test_go_interop.py +76 -0
  33. hessian2-0.1.0/crates/hessian2-python/tests/test_java_interop.py +339 -0
  34. hessian2-0.1.0/crates/hessian2-python/tests/test_nested.py +419 -0
  35. hessian2-0.1.0/crates/hessian2-python/tests/test_properties.py +136 -0
  36. hessian2-0.1.0/crates/hessian2-python/tests/test_protocol_review.py +173 -0
  37. hessian2-0.1.0/crates/hessian2-python/tests/test_readme.py +41 -0
  38. hessian2-0.1.0/crates/hessian2-python/tests/test_review2.py +340 -0
  39. hessian2-0.1.0/crates/hessian2-python/tests/test_review2_java.py +167 -0
  40. hessian2-0.1.0/crates/hessian2-python/tests/test_security.py +482 -0
  41. hessian2-0.1.0/crates/hessian2-python/tests/test_stdlib.py +220 -0
  42. hessian2-0.1.0/pyproject.toml +61 -0
  43. hessian2-0.1.0/python/hessian2/__init__.py +246 -0
  44. hessian2-0.1.0/python/hessian2/_core.pyi +68 -0
  45. 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.
@@ -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
+ [![CI](https://github.com/cnzakii/hessian2/actions/workflows/ci.yml/badge.svg)](https://github.com/cnzakii/hessian2/actions/workflows/ci.yml)
35
+ [![Coverage checks](https://github.com/cnzakii/hessian2/actions/workflows/coverage.yml/badge.svg)](https://github.com/cnzakii/hessian2/actions/workflows/coverage.yml)
36
+ [![Codecov](https://codecov.io/gh/cnzakii/hessian2/branch/main/graph/badge.svg)](https://codecov.io/gh/cnzakii/hessian2)
37
+ ![Python 3.11+](https://img.shields.io/badge/python-3.11%2B-blue)
38
+ ![Rust 1.88+](https://img.shields.io/badge/rust-1.88%2B-orange)
39
+ [![MIT license](https://img.shields.io/badge/license-MIT-green)](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
+