@@ -4252,6 +4252,50 @@ fn struct_output_body(
42524252 format ! ( "{}/{}" , ext. java_class_prefix, struct_name)
42534253 } ;
42544254
4255+ // A `value_class` is JVM-erased to its single wrapped field, so its JObject
4256+ // form is that inner field's encoded value (e.g. `ZBytes`/`ZenohId` → a
4257+ // `[B` byte array), never a boxed wrapper object — Kotlin never
4258+ // materializes the wrapper. Boxing it via `new_object` would produce an
4259+ // object the consumer reinterprets as the erased inner type (e.g. a
4260+ // `ZBytes` stored into a `byte[]` slot → corrupt array). The data-class
4261+ // encoder already computes the erased inner descriptor for value-class
4262+ // fields, so emit the inner converter here to match. Only object-shaped
4263+ // inners can be returned as a bare JObject; primitive-backed value classes
4264+ // would need boxing and none exist today, so those fall through.
4265+ //
4266+ // The inner field is *moved* into the (by-value) inner converter — `v` is
4267+ // owned here and the single field is never read again. Moving (vs cloning)
4268+ // avoids a redundant copy of the payload and imposes no `Clone` bound on
4269+ // the inner type. A value class that implemented `Drop` would block the
4270+ // partial move with a clear compile error, but newtype wrappers don't.
4271+ if ext
4272+ . types
4273+ . get ( & TypeKey :: from_type ( & struct_ty) )
4274+ . map ( |c| c. value_class )
4275+ . unwrap_or ( false )
4276+ {
4277+ if let syn:: Fields :: Named ( named) = & s. fields {
4278+ if let Some ( inner) = named. named . first ( ) {
4279+ if let ( Some ( inner_ident) , Some ( inner_entry) ) =
4280+ ( inner. ident . clone ( ) , registry. output_entry ( & inner. ty ) )
4281+ {
4282+ if matches ! (
4283+ jni_field_access( & inner_entry. destination) ,
4284+ Some ( ( _, _, true ) ) | None
4285+ ) {
4286+ let inner_conv = inner_entry. function . sig . ident . clone ( ) ;
4287+ let body: syn:: Expr = syn:: parse_quote!( {
4288+ let __inner = #inner_conv( env, v. #inner_ident) ?;
4289+ let __obj: jni:: objects:: JObject = __inner. into( ) ;
4290+ __obj
4291+ } ) ;
4292+ return Some ( ( syn:: parse_quote!( jni:: objects:: JObject ) , body) ) ;
4293+ }
4294+ }
4295+ }
4296+ }
4297+ }
4298+
42554299 let syn:: Fields :: Named ( named) = & s. fields else {
42564300 return None ;
42574301 } ;
0 commit comments