Skip to content

Commit f23c08e

Browse files
authored
Class converter improvements (#112)
1 parent 773b670 commit f23c08e

File tree

5 files changed

+178
-15
lines changed

5 files changed

+178
-15
lines changed

src/main/java/com/surrealdb/ValueClassConverter.java

Lines changed: 61 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,7 @@
33
import java.lang.reflect.Field;
44
import java.lang.reflect.ParameterizedType;
55
import java.lang.reflect.Type;
6-
import java.util.ArrayList;
7-
import java.util.List;
8-
import java.util.Optional;
6+
import java.util.*;
97

108
class ValueClassConverter<T> {
119

@@ -16,6 +14,8 @@ class ValueClassConverter<T> {
1614
}
1715

1816
private static java.lang.Object convertSingleValue(final Value value) {
17+
if (value.isNull())
18+
return null;
1919
if (value.isBoolean())
2020
return value.getBoolean();
2121
if (value.isDouble())
@@ -42,7 +42,9 @@ private static java.lang.Object convertSingleValue(final Value value) {
4242
}
4343

4444
private static <T> void setSingleValue(final Field field, final Class<?> type, final T target, final Value value) throws IllegalAccessException {
45-
if (value.isBoolean()) {
45+
if (value.isNull()) {
46+
field.set(target, null);
47+
} else if (value.isBoolean()) {
4648
field.setBoolean(target, value.getBoolean());
4749
} else if (value.isDouble()) {
4850
final double d = value.getDouble();
@@ -94,7 +96,7 @@ else if (type == Short.class)
9496

9597
private static java.lang.Object convertArrayValue(final Field field, final Value value) throws ReflectiveOperationException {
9698
if (value.isObject()) {
97-
final Class<?> subType = getGenericType(field);
99+
final Class<?> subType = getGenericType(field, 0);
98100
if (subType == null) {
99101
throw new SurrealException("Unsupported field type: " + field);
100102
}
@@ -115,19 +117,42 @@ private static <T> T convert(Class<T> clazz, Object source) throws ReflectiveOpe
115117
for (final Entry entry : source) {
116118
try {
117119
final String key = entry.getKey();
118-
final Field field = clazz.getField(key);
119120
final Value value = entry.getValue();
120-
field.setAccessible(true);
121+
final Field field = getInheritedDeclaredField(clazz, key);
121122
final Class<?> type = field.getType();
122-
if (value.isArray()) {
123+
124+
field.setAccessible(true);
125+
126+
if (Value.class.equals(type)) {
127+
field.set(target, value);
128+
} else if (value.isArray()) {
123129
final List<java.lang.Object> arrayList = new ArrayList<>();
124130
for (final Value elementValue : value.getArray()) {
125131
arrayList.add(convertArrayValue(field, elementValue));
126132
}
127133
setFieldObject(field, type, target, arrayList);
128134
} else if (value.isObject()) {
129-
java.lang.Object o = convert(type, value.getObject());
130-
setFieldObject(field, type, target, o);
135+
if (Map.class.isAssignableFrom(type)) {
136+
final Map<String, java.lang.Object> map = new HashMap<>();
137+
final Class<?> subType = getGenericType(field, 1);
138+
if (subType == null) {
139+
throw new SurrealException("Unsupported field type: " + field);
140+
}
141+
for (final Entry mapEntry : value.getObject()) {
142+
final String entryKey = mapEntry.getKey();
143+
final Value entryValue = mapEntry.getValue();
144+
// todo - array support
145+
if (entryValue.isObject()) {
146+
map.put(entryKey, convert(subType, entryValue.getObject()));
147+
} else {
148+
map.put(entryKey, convertSingleValue(entryValue));
149+
}
150+
}
151+
setFieldObject(field, type, target, map);
152+
} else {
153+
java.lang.Object o = convert(type, value.getObject());
154+
setFieldObject(field, type, target, o);
155+
}
131156
} else {
132157
setFieldSingleValue(field, type, target, value);
133158
}
@@ -148,30 +173,52 @@ private static <T, V> void setFieldObject(Field field, Class<?> type, T target,
148173

149174
private static <T, V> void setFieldSingleValue(Field field, Class<?> type, T target, Value value) throws ReflectiveOperationException {
150175
if (Optional.class.equals(type)) {
151-
field.set(target, convertSingleValue(value));
176+
final java.lang.Object converted = convertSingleValue(value);
177+
if (converted == null) {
178+
field.set(target, Optional.empty());
179+
} else {
180+
field.set(target, Optional.of(converted));
181+
}
152182
} else {
153183
setSingleValue(field, type, target, value);
154184
}
155185
}
156186

157-
static Class<?> getGenericType(final Field field) {
187+
static Class<?> getGenericType(final Field field, final int index) {
158188
// Check if the field is parameterized
159189
if (field.getGenericType() instanceof ParameterizedType) {
160190
ParameterizedType parameterizedType = (ParameterizedType) field.getGenericType();
161191

162192
// Get the actual type arguments (generics)
163193
final Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
164194

165-
if (actualTypeArguments.length > 0) {
195+
if (actualTypeArguments.length > index) {
166196
// Return the first type argument
167-
return (Class<?>) actualTypeArguments[0];
197+
return (Class<?>) actualTypeArguments[index];
168198
}
169199
}
170200
return null;
171201
}
172202

203+
static Field getInheritedDeclaredField(Class<?> clazz, String fieldName) throws NoSuchFieldException {
204+
while (clazz != null) {
205+
try {
206+
return clazz.getDeclaredField(fieldName);
207+
} catch (NoSuchFieldException e) {
208+
clazz = clazz.getSuperclass();
209+
}
210+
}
211+
throw new NoSuchFieldException("Field '" + fieldName + "' not found in class hierarchy.");
212+
}
213+
173214
final T convert(final Value value) {
174215
try {
216+
if (value.isNone() || value.isNull())
217+
return null;
218+
219+
if (!value.isObject())
220+
throw new SurrealException("Unexpected value: " + value);
221+
175222
return convert(clazz, value.getObject());
176223
} catch (ReflectiveOperationException e) {
177224
throw new SurrealException("Failed to create instance of " + clazz.getName(), e);

src/test/java/com/surrealdb/ExampleTests.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ void example() {
2727
}
2828

2929
static class Person {
30-
String id;
30+
RecordId id;
3131
String title;
3232
String firstName;
3333
String lastName;

src/test/java/com/surrealdb/QueryTests.java

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
package com.surrealdb;
22

3+
import com.surrealdb.pojos.Dates;
4+
import com.surrealdb.pojos.Partial;
35
import com.surrealdb.pojos.Person;
6+
import com.surrealdb.pojos.Stats;
47
import org.junit.jupiter.api.Test;
58

69
import java.awt.geom.Point2D;
@@ -121,6 +124,43 @@ void queryPrimitiveFields() throws SurrealException {
121124
}
122125
}
123126

127+
@Test
128+
void queryNull() throws SurrealException {
129+
try (final Surreal surreal = new Surreal()) {
130+
surreal.connect("memory").useNs("test_ns").useDb("test_db");
131+
{
132+
final String sql = "SELECT * FROM ONLY person:unknown";
133+
final Response response = surreal.query(sql);
134+
{
135+
final Value value = response.take(0);
136+
assertTrue(value.isNone());
137+
}
138+
{
139+
final Person person = response.take(Person.class, 0);
140+
assertNull(person);
141+
}
142+
}
143+
}
144+
}
145+
146+
@Test
147+
void queryClassMap() throws SurrealException {
148+
try (final Surreal surreal = new Surreal()) {
149+
surreal.connect("memory").useNs("test_ns").useDb("test_db");
150+
{
151+
final String sql = "CREATE ONLY stats:1 SET statistics.archery = 100, statistics.golf = 70, statistics.running = 120, sessions.example = { duration: 2h, dateTime: d\"2023-07-03T07:18:52Z\" }, something = true;";
152+
final Response response = surreal.query(sql);
153+
final Stats stats = response.take(Stats.class, 0);
154+
assertEquals(100L, stats.statistics.get("archery"));
155+
assertEquals(70L, stats.statistics.get("golf"));
156+
assertEquals(120L, stats.statistics.get("running"));
157+
final Dates sessions = stats.sessions.get("example");
158+
assertEquals(2, sessions.duration.toHours());
159+
assertEquals("2023-07-03T07:18:52Z[UTC]", sessions.dateTime.toString());
160+
}
161+
}
162+
}
163+
124164
@Test
125165
void queryUuid() throws SurrealException {
126166
try (final Surreal surreal = new Surreal()) {
@@ -460,4 +500,17 @@ void queryWithValueMut() throws SurrealException {
460500
}
461501
}
462502
}
503+
504+
@Test
505+
void queryValue() throws SurrealException {
506+
try (final Surreal surreal = new Surreal()) {
507+
surreal.connect("memory").useNs("test_ns").useDb("test_db");
508+
{
509+
final String sql = "RETURN { inner: true }";
510+
final Response response = surreal.query(sql);
511+
final Partial partial = response.take(Partial.class, 0);
512+
assertTrue(partial.inner.getBoolean());
513+
}
514+
}
515+
}
463516
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package com.surrealdb.pojos;
2+
3+
import com.surrealdb.Value;
4+
5+
import java.util.Objects;
6+
7+
public class Partial {
8+
9+
public Value inner;
10+
11+
@Override
12+
public int hashCode() {
13+
return Objects.hash(inner);
14+
}
15+
16+
@Override
17+
public boolean equals(Object o) {
18+
if (this == o) return true;
19+
if (o == null || getClass() != o.getClass()) return false;
20+
final Partial d = (Partial) o;
21+
return
22+
Objects.equals(inner, d.inner);
23+
}
24+
25+
@Override
26+
public String toString() {
27+
return "inner: " + inner;
28+
}
29+
30+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package com.surrealdb.pojos;
2+
3+
import java.time.Duration;
4+
import java.time.ZonedDateTime;
5+
import java.util.HashMap;
6+
import java.util.Objects;
7+
8+
public class Stats {
9+
10+
public HashMap<String, Long> statistics;
11+
public HashMap<String, Dates> sessions;
12+
13+
@Override
14+
public int hashCode() {
15+
return Objects.hash(statistics, sessions);
16+
}
17+
18+
@Override
19+
public boolean equals(Object o) {
20+
if (this == o) return true;
21+
if (o == null || getClass() != o.getClass()) return false;
22+
final Stats d = (Stats) o;
23+
return
24+
Objects.equals(statistics, d.statistics) &&
25+
Objects.equals(sessions, d.sessions);
26+
}
27+
28+
@Override
29+
public String toString() {
30+
return "statistics: " + statistics + ", sessions: " + sessions;
31+
}
32+
33+
}

0 commit comments

Comments
 (0)