From 4f9188bcf09bcbd5467066f93d44b4255bec94fd Mon Sep 17 00:00:00 2001 From: Miguel Angel Maciel Date: Mon, 2 Mar 2026 20:52:05 -0500 Subject: [PATCH 1/2] Support private record serialization --- .../java/com/squareup/moshi/records/RecordsTest.java | 11 +++++++++++ .../com/squareup/moshi/internal/RecordJsonAdapter.kt | 6 ++++-- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/moshi/records-tests/src/test/java/com/squareup/moshi/records/RecordsTest.java b/moshi/records-tests/src/test/java/com/squareup/moshi/records/RecordsTest.java index 00f613f0d..c31cc5875 100644 --- a/moshi/records-tests/src/test/java/com/squareup/moshi/records/RecordsTest.java +++ b/moshi/records-tests/src/test/java/com/squareup/moshi/records/RecordsTest.java @@ -290,5 +290,16 @@ public void nullObjectIsNull() throws IOException { assertThat(adapter.toJson(value)).isEqualTo("{\"i\":5}"); } + @Test + public void privateRecordSerializesCorrectly() throws IOException { + var adapter = moshi.adapter(PrivateRecord.class); + String json = "{\"id\":\"1\",\"value\":123}"; + PrivateRecord privateRecord = new PrivateRecord("1", 123); + assertThat(adapter.fromJson(json)).isEqualTo(privateRecord); + assertThat(adapter.toJson(privateRecord)).isEqualTo(json); + } + + private record PrivateRecord(String id, int value) {} + public record AbsentValues(String s, int i) {} } diff --git a/moshi/src/main/java16/com/squareup/moshi/internal/RecordJsonAdapter.kt b/moshi/src/main/java16/com/squareup/moshi/internal/RecordJsonAdapter.kt index 157466c67..304d0330f 100644 --- a/moshi/src/main/java16/com/squareup/moshi/internal/RecordJsonAdapter.kt +++ b/moshi/src/main/java16/com/squareup/moshi/internal/RecordJsonAdapter.kt @@ -22,7 +22,6 @@ import com.squareup.moshi.Moshi import com.squareup.moshi.rawType import java.lang.invoke.MethodHandle import java.lang.invoke.MethodHandles -import java.lang.invoke.MethodType.methodType import java.lang.reflect.InvocationTargetException import java.lang.reflect.ParameterizedType import java.lang.reflect.RecordComponent @@ -144,7 +143,9 @@ internal class RecordJsonAdapter( val constructor = try { - lookup.findConstructor(rawType, methodType(VOID_CLASS, componentRawTypes)) + lookup.unreflectConstructor( + rawType.getDeclaredConstructor(*componentRawTypes).apply { isAccessible = true } + ) } catch (e: NoSuchMethodException) { throw AssertionError(e) } catch (e: IllegalAccessException) { @@ -170,6 +171,7 @@ internal class RecordJsonAdapter( val accessor = try { + component.accessor.isAccessible = true lookup.unreflect(component.accessor) } catch (e: IllegalAccessException) { throw AssertionError(e) From 3de044b7d39d0c37c5089c379ab7ae6c6e082765 Mon Sep 17 00:00:00 2001 From: Miguel Angel Maciel Date: Fri, 6 Mar 2026 11:39:02 -0500 Subject: [PATCH 2/2] Update to privateLookupIn for private access --- .../squareup/moshi/internal/RecordJsonAdapter.kt | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/moshi/src/main/java16/com/squareup/moshi/internal/RecordJsonAdapter.kt b/moshi/src/main/java16/com/squareup/moshi/internal/RecordJsonAdapter.kt index 304d0330f..f19911d97 100644 --- a/moshi/src/main/java16/com/squareup/moshi/internal/RecordJsonAdapter.kt +++ b/moshi/src/main/java16/com/squareup/moshi/internal/RecordJsonAdapter.kt @@ -22,6 +22,7 @@ import com.squareup.moshi.Moshi import com.squareup.moshi.rawType import java.lang.invoke.MethodHandle import java.lang.invoke.MethodHandles +import java.lang.invoke.MethodType.methodType import java.lang.reflect.InvocationTargetException import java.lang.reflect.ParameterizedType import java.lang.reflect.RecordComponent @@ -127,7 +128,12 @@ internal class RecordJsonAdapter( val components = rawType.recordComponents val bindings = LinkedHashMap>() - val lookup = MethodHandles.lookup() + var lookup = MethodHandles.lookup() + try { + lookup = MethodHandles.privateLookupIn(rawType, lookup) + } catch (_: IllegalAccessException) { + // fallback to standard lookup + } val componentRawTypes = Array>(components.size) { i -> val component = components[i] @@ -143,9 +149,7 @@ internal class RecordJsonAdapter( val constructor = try { - lookup.unreflectConstructor( - rawType.getDeclaredConstructor(*componentRawTypes).apply { isAccessible = true } - ) + lookup.findConstructor(rawType, methodType(VOID_CLASS, componentRawTypes)) } catch (e: NoSuchMethodException) { throw AssertionError(e) } catch (e: IllegalAccessException) { @@ -171,7 +175,6 @@ internal class RecordJsonAdapter( val accessor = try { - component.accessor.isAccessible = true lookup.unreflect(component.accessor) } catch (e: IllegalAccessException) { throw AssertionError(e)