@@ -67,6 +67,18 @@ it, and avoids a mismatch between the referenced object and the transformed one.
6767The ` v2 ` implementation provides a new API for the object transformers.
6868Please look at the * Usage (V2)* section in this file.
6969
70+ ### Object transformers V3
71+
72+ | Implementations | Version |
73+ | -----------------| ----------|
74+ | ` v3 ` | ` 0.5.0+ ` |
75+
76+ The ` v3 ` implementation is a full rewrite targeting ** Python 3.12+** .
77+ It uses ` dataclasses ` , structural pattern matching (` match/case ` ) and PEP 604
78+ union types. Its API is intentionally similar to ` v2 ` but fixes several
79+ correctness issues and adds stricter safety limits.
80+ Please look at the * Usage (V3)* and * Migration to V3* sections in this file.
81+
7082### Bytes arrays
7183
7284| Implementations | Version |
@@ -98,7 +110,8 @@ You can find a sample usage in the *Custom Transformer* section in this file.
98110
99111## Requirements
100112
101- * Python >= 2.7 or Python >= 3.4
113+ * Python >= 2.7 or Python >= 3.4 for ` v1 ` and ` v2 `
114+ * Python >= 3.12 for ` v3 `
102115* ` enum34 ` and ` typing ` when using Python <= 3.4 (installable with ` pip ` )
103116* Maven 2+ (for building test data of serialized objects.
104117 You can skip it if you do not plan to run ` tests.py ` )
@@ -480,3 +493,143 @@ pobj = javaobj.loads("custom_objects.ser", *transformers)
480493# it's static. See: https://stackoverflow.com/a/16477421/12621168
481494print (pobj.field_data[" int_not_in_fields" ])
482495```
496+
497+ ## Usage (V3 implementation)
498+
499+ > ** Requires Python 3.12+.**
500+
501+ The ` javaobj.v3 ` package is a full rewrite of the Java object stream parser.
502+ It provides the same two entry-points as ` v2 ` :
503+
504+ * ` load(fd, *transformers, use_numpy_arrays=False, max_array_size=…, max_depth=500) ` :
505+ Parses a binary file descriptor opened in ` rb ` mode and returns the top-level
506+ object if the stream contains exactly one, a list of objects if there are
507+ several, or ` None ` for an empty stream. Pass additional ` ObjectTransformer `
508+ instances as positional arguments.
509+
510+ * ` loads(data, *transformers, …) ` :
511+ Convenience wrapper around ` load() ` that accepts ` bytes ` .
512+
513+ Sample usage:
514+
515+ ``` python
516+ import javaobj.v3 as javaobj
517+
518+ with open (" obj5.ser" , " rb" ) as fd:
519+ pobj = javaobj.load(fd)
520+
521+ # Access fields by name (preferred)
522+ value = pobj.get_field(" myField" )
523+
524+ # Or use attribute-style access (issues a warning on ambiguity)
525+ value = pobj.myField
526+ ```
527+
528+ ### New features in V3
529+
530+ | Feature | V1 | V2 | V3 |
531+ | ---| ---| ---| ---|
532+ | Python 3.12+ (` match/case ` , PEP 604) | ✗ | ✗ | ✓ |
533+ | Fully typed (` dataclasses ` , ` TypeAlias ` ) | ✗ | partial | ✓ |
534+ | ` TC_RESET ` handling | ✗ | ✗ | ✓ |
535+ | ` TC_EXCEPTION ` in object graph | ✗ | ✗ | ✓ |
536+ | ` TC_PROXYCLASSDESC ` | ✗ | ✓ | ✓ |
537+ | Security limits (max depth / array size) | ✗ | ✗ | ✓ |
538+ | Correct ` TYPE_CHAR ` numpy dtype (` >u2 ` ) | ✗ | ✗ | ✓ |
539+ | Typed exception hierarchy | ✗ | ✗ | ✓ |
540+ | ` BlockData.__eq__(bytes) ` compatibility | ✓ | ✓ | ✓ |
541+
542+ ### Security limits
543+
544+ ` v3 ` adds two optional safety limits that prevent resource exhaustion when
545+ parsing untrusted streams:
546+
547+ ``` python
548+ import javaobj.v3 as javaobj
549+
550+ with open (" untrusted.ser" , " rb" ) as fd:
551+ pobj = javaobj.load(
552+ fd,
553+ max_array_size = 10 * 1024 * 1024 , # 10 MiB max per array
554+ max_depth = 100 , # max object-graph depth
555+ )
556+ ```
557+
558+ ### Object Transformer V3
559+
560+ The ` ObjectTransformer ` base class in ` v3 ` has the same three override points
561+ as in ` v2 ` :
562+
563+ * ` create_instance(classdesc) ` — return a ` JavaInstance ` subclass (or ` None `
564+ to fall back to the next transformer).
565+ * ` load_array(reader, type_code, size) ` — called for ` TC_ARRAY ` records;
566+ return the array data (` bytes ` or ` list ` ) or ` None ` to use the default logic.
567+ * ` load_custom_writeObject(parser, reader, class_name) ` — called when a
568+ class written with ` writeObject() ` requires fully custom parsing.
569+
570+ The ` DefaultObjectTransformer ` additionally exposes a public ` handles(name) `
571+ method that returns ` True ` when the transformer knows how to load the given
572+ Java class name.
573+
574+ ### Using NumPy arrays (V3)
575+
576+ ``` python
577+ import javaobj.v3 as javaobj
578+
579+ with open (" arrays.ser" , " rb" ) as fd:
580+ pobj = javaobj.load(fd, use_numpy_arrays = True )
581+ ```
582+
583+ When ` use_numpy_arrays=True ` , a ` NumpyArrayTransformer ` is appended to the
584+ transformer list and primitive arrays are returned as ` numpy.ndarray ` .
585+
586+ ---
587+
588+ ## Migration to V3
589+
590+ ### From V1 to V3
591+
592+ | V1 | V3 |
593+ | ---| ---|
594+ | ` import javaobj ` | ` import javaobj.v3 as javaobj ` |
595+ | ` pobj.classdesc.name ` | ` pobj.classdesc.name ` (unchanged) |
596+ | ` pobj.myField ` (direct attribute) | ` pobj.get_field("myField") ` (preferred) or ` pobj.myField ` |
597+ | ` pobj._data ` on arrays | ` pobj.data ` (public) |
598+ | ` javaobj.JavaObjectUnmarshaller ` | removed — use ` javaobj.v3.parser.JavaStreamParser ` |
599+ | ` javaobj.JavaObjectMarshaller ` | marshalling not available in ` v3 ` |
600+ | Exceptions: bare ` Exception ` | Typed: ` ParseError ` , ` UnexpectedOpcodeError ` , … |
601+
602+ Shallow conversion helper (best-effort, for gradual migration):
603+
604+ ``` python
605+ from javaobj.v3._compat import v1_to_v3
606+ v3_obj = v1_to_v3(v1_obj)
607+ ```
608+
609+ ### From V2 to V3
610+
611+ | V2 | V3 |
612+ | ---| ---|
613+ | ` import javaobj.v2 as javaobj ` | ` import javaobj.v3 as javaobj ` |
614+ | ` javaobj.load(fd) ` | ` javaobj.load(fd) ` (same signature) |
615+ | ` javaobj.loads(data) ` | ` javaobj.loads(data) ` (same signature) |
616+ | ` pobj.classdesc.name ` | ` pobj.classdesc.name ` (unchanged) |
617+ | ` pobj.field_data[cd][field] ` | ` pobj.field_data[cd][field] ` (unchanged) |
618+ | ` pobj.get_field("name") ` | ` pobj.get_field("name") ` (unchanged) |
619+ | ` pobj.__getattr__ ` ambiguity silent | warns when field exists in multiple classes |
620+ | ` transformer._type_mapper ` (private) | ` transformer.handles(name) ` (public) |
621+ | ` JavaArray.data ` (` tuple ` of ints for bytes) | ` JavaArray.data ` (` bytes ` for ` TYPE_BYTE ` ) |
622+ | ` BlockData ` compared with ` bytes ` | ` BlockData.__eq__(bytes) ` still works |
623+ | ` use_numpy_arrays=True ` (v2 option) | ` use_numpy_arrays=True ` (same) |
624+ | No depth/size limits | ` max_depth=500 ` , ` max_array_size=100 MiB ` |
625+ | No typed exceptions | ` ParseError ` , ` SecurityError ` , … |
626+
627+ Shallow conversion helper (best-effort, for gradual migration):
628+
629+ ``` python
630+ from javaobj.v3._compat import v2_to_v3
631+ v3_obj = v2_to_v3(v2_obj)
632+ ```
633+
634+ > ** Note:** ` v3 ` requires ** Python 3.12+** and does ** not** support marshalling
635+ > (writing). If you need to write Java object streams, use ` v1 ` .
0 commit comments