Fix: Support Java record accessors in JSONObject

This commit is contained in:
AbhineshJha
2025-10-25 14:16:55 +05:30
parent 25f355a953
commit 20f5200000
3 changed files with 79 additions and 1 deletions

View File

@@ -1885,7 +1885,8 @@ public class JSONObject {
} }
private static boolean isValidMethodName(String name) { private static boolean isValidMethodName(String name) {
return !"getClass".equals(name) && !"getDeclaringClass".equals(name); return !"getClass".equals(name)
&& !"getDeclaringClass".equals(name);
} }
private static String getKeyNameFromMethod(Method method) { private static String getKeyNameFromMethod(Method method) {
@@ -1909,6 +1910,32 @@ public class JSONObject {
} else if (name.startsWith("is") && name.length() > 2) { } else if (name.startsWith("is") && name.length() > 2) {
key = name.substring(2); key = name.substring(2);
} else { } else {
// Check if this is a record-style accessor (no prefix)
// Record accessors are simple method names that match field names
// They must start with a lowercase letter and should be declared in the class itself
// (not inherited from Object, Enum, Number, or any java.* class)
// Also exclude common Object/bean method names
Class<?> declaringClass = method.getDeclaringClass();
if (name.length() > 0 && Character.isLowerCase(name.charAt(0))
&& !"get".equals(name)
&& !"is".equals(name)
&& !"set".equals(name)
&& !"toString".equals(name)
&& !"hashCode".equals(name)
&& !"equals".equals(name)
&& !"clone".equals(name)
&& !"notify".equals(name)
&& !"notifyAll".equals(name)
&& !"wait".equals(name)
&& declaringClass != null
&& declaringClass != Object.class
&& !Enum.class.isAssignableFrom(declaringClass)
&& !Number.class.isAssignableFrom(declaringClass)
&& !declaringClass.getName().startsWith("java.")
&& !declaringClass.getName().startsWith("javax.")) {
// This is a record-style accessor - return the method name as-is
return name;
}
return null; return null;
} }
// if the first letter in the key is not uppercase, then skip. // if the first letter in the key is not uppercase, then skip.

View File

@@ -51,6 +51,7 @@ import org.json.junit.data.MyJsonString;
import org.json.junit.data.MyNumber; import org.json.junit.data.MyNumber;
import org.json.junit.data.MyNumberContainer; import org.json.junit.data.MyNumberContainer;
import org.json.junit.data.MyPublicClass; import org.json.junit.data.MyPublicClass;
import org.json.junit.data.PersonRecord;
import org.json.junit.data.RecursiveBean; import org.json.junit.data.RecursiveBean;
import org.json.junit.data.RecursiveBeanEquals; import org.json.junit.data.RecursiveBeanEquals;
import org.json.junit.data.Singleton; import org.json.junit.data.Singleton;
@@ -796,6 +797,25 @@ public class JSONObjectTest {
Util.checkJSONObjectMaps(jsonObject); Util.checkJSONObjectMaps(jsonObject);
} }
/**
* JSONObject built from a Java record.
* Records use accessor methods without get/is prefixes (e.g., name() instead of getName()).
* This test verifies that JSONObject correctly handles record types.
*/
@Test
public void jsonObjectByRecord() {
PersonRecord person = new PersonRecord("John Doe", 30, true);
JSONObject jsonObject = new JSONObject(person);
// validate JSON
Object doc = Configuration.defaultConfiguration().jsonProvider().parse(jsonObject.toString());
assertTrue("expected 3 top level items", ((Map<?,?>)(JsonPath.read(doc, "$"))).size() == 3);
assertTrue("expected name field", "John Doe".equals(jsonObject.query("/name")));
assertTrue("expected age field", Integer.valueOf(30).equals(jsonObject.query("/age")));
assertTrue("expected active field", Boolean.TRUE.equals(jsonObject.query("/active")));
Util.checkJSONObjectMaps(jsonObject);
}
/** /**
* A bean is also an object. But in order to test the JSONObject * A bean is also an object. But in order to test the JSONObject
* ctor that takes an object and a list of names, * ctor that takes an object and a list of names,

View File

@@ -0,0 +1,31 @@
package org.json.junit.data;
/**
* A test class that mimics Java record accessor patterns.
* Records use accessor methods without get/is prefixes (e.g., name() instead of getName()).
* This class simulates that behavior to test JSONObject's handling of such methods.
*/
public class PersonRecord {
private final String name;
private final int age;
private final boolean active;
public PersonRecord(String name, int age, boolean active) {
this.name = name;
this.age = age;
this.active = active;
}
// Record-style accessors (no "get" or "is" prefix)
public String name() {
return name;
}
public int age() {
return age;
}
public boolean active() {
return active;
}
}