Skip to content

Commit 4f46470

Browse files
dmealingclaude
andcommitted
fix(metadata): close the DATE-array storage gap left by #275
Two root defects in the JSON array-write storage path, deliberately left unfixed by the #275 batch as out-of-scope scope-creep triggers: - MetaField.setObject(Object,Object) converted via the field's SCALAR getDataType() instead of the array-aware getEffectiveDataType(), so an isArray field's List value was corrupted (comma-joined / bracketed toString()) before setObjectAttribute's own instanceof check rejected it. Broke setBoolean/setInt/setLong/setDouble/setStringArray, and every MetaObjectDeserializer array-read branch that routes through it. - DataConverter had no DATE_ARRAY case (the commented-out `//toDateArray(val)` fragment), so no entry point could store a List<Date> on an isArray field.date/field.timestamp field. Fix: getEffectiveDataType() in MetaField.setObject (a strict no-op for every non-array field); a new DataConverter.toDateArray mirroring the sibling toLongArray/toBooleanArray shape, wired into case DATE_ARRAY. BYTE_ARRAY/SHORT_ARRAY stay on the unsupported arm (field.byte/field.short are non-functional stubs). Also closes the deferred findings this unblocked: MetaObjectSerializer's DATE-array element loop now converts via DataConverter.toDate(o) instead of a hard cast; field.timestamp (incl. @localTime) array coverage; a full-Gson-pipeline round trip for the previously-untestable DATE-array read path in MetaObjectDeserializer; null-array-itself pins extended to every touched type; and a stale MetaObjectDeserializer comment describing the DATE-array branch as blocked, now rewritten to match reality. Blast-radius checked: every MetaField.setObject caller either passes a scalar value (no-op under the fix) or already routed around the array bug via setObjectArray/setValue; no caller depended on the corrupting conversion succeeding. Refs #275 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015TqsuDye2SfXGf43vuoD3n
1 parent ce90dab commit 4f46470

7 files changed

Lines changed: 302 additions & 76 deletions

File tree

server/java/metadata/src/main/java/com/metaobjects/field/MetaField.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1089,7 +1089,11 @@ public void setDate(Object obj, Date value) {
10891089
}
10901090

10911091
public void setObject(Object obj, Object value) {
1092-
setObjectAttribute(obj, DataConverter.toType(getDataType(), value ));
1092+
// ADR-0039: use the RESOLVING, array-aware getEffectiveDataType() -- the field's SCALAR
1093+
// getDataType() corrupted a List for an isArray field (e.g. comma-joining a STRING array
1094+
// into a single string) before setObjectAttribute's own instanceof check rejected it.
1095+
// getEffectiveDataType() is a strict no-op for every non-array field (#275 carry-forward).
1096+
setObjectAttribute(obj, DataConverter.toType(getEffectiveDataType(), value ));
10931097
}
10941098

10951099
public void setObjectArray(Object obj, List<?> value) {

server/java/metadata/src/main/java/com/metaobjects/io/object/gson/MetaObjectDeserializer.java

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -126,14 +126,12 @@ protected void readFieldValue(MetaObject mo, MetaField mf, Object vo,
126126
// nothing that parsed before stops parsing). String -> tolerant ISO parse
127127
// (TemporalWireFormat). Array: element-wise into a List<Date> via
128128
// setObjectArray, skipping context.deserialize(el, List.class), which yields a
129-
// type-losing List<Double>. setObjectArray only bypasses MetaField's OWN
130-
// DataConverter.toType call; setObjectAttribute still routes through
131-
// AbstractObjectRepresentation.setValue, which unconditionally applies
132-
// DataConverter.toType(effectiveDataType, value) -- and DataConverter's
133-
// DATE_ARRAY case is unimplemented (unsupported()). So today this branch
134-
// throws UnsupportedOperationException for a non-empty date array on the
135-
// default (non-proxy) representation path; it becomes correct once
136-
// DataConverter grows a DATE_ARRAY conversion.
129+
// type-losing List<Double>. setObjectArray routes through
130+
// AbstractObjectRepresentation.setValue, which applies
131+
// DataConverter.toType(effectiveDataType, value) -- backed, since the #275
132+
// carry-forward unit, by DataConverter.toDateArray. This branch genuinely
133+
// round-trips a date array end to end today (see
134+
// GsonArrayWriteRoundTripTest's Step 3b/A6 coverage).
137135
if (mf.isArrayType() && el.isJsonArray()) {
138136
List<Date> dates = new ArrayList<>();
139137
for (JsonElement item : el.getAsJsonArray()) {

server/java/metadata/src/main/java/com/metaobjects/io/object/gson/MetaObjectSerializer.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import com.metaobjects.loader.MetaDataLoader;
99
import com.metaobjects.object.MetaObject;
1010
import com.metaobjects.object.MetaObjectAware;
11+
import com.metaobjects.util.DataConverter;
1112
import com.google.gson.*;
1213

1314
import static com.metaobjects.io.json.JsonIOUtil.*;
@@ -94,7 +95,13 @@ protected void writeField(MetaObject mo, MetaField mf, Object vo,
9495
} else {
9596
JsonArray arr = new JsonArray();
9697
for (Object o : dates) {
97-
java.util.Date d = (java.util.Date) o;
98+
// C1: route through the shared DataConverter.toDate(Object) rather
99+
// than a hard (Date) cast -- matches the non-array DATE branch below
100+
// (mf.getDate(vo) is itself backed by DataConverter.toDate), and
101+
// accepts the same scalar inputs that converter always has, instead
102+
// of throwing a bare ClassCastException for anything not already a
103+
// java.util.Date.
104+
java.util.Date d = DataConverter.toDate(o);
98105
if (d == null) arr.add(JsonNull.INSTANCE);
99106
else arr.add(TemporalWireFormat.format(mf, d));
100107
}

server/java/metadata/src/main/java/com/metaobjects/util/DataConverter.java

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,9 @@ public static Object toType( DataTypes dataType, Object val ) {
4747

4848
case BYTE_ARRAY://return toByteArray( val );
4949
case SHORT_ARRAY: //return toShortArray( val );
50-
case DATE_ARRAY: //toDateArray( val );
5150
return unsupported(dataType,val);
5251

52+
case DATE_ARRAY: return toDateArray( val );
5353
case STRING_ARRAY: return toStringArray( val );
5454
case OBJECT_ARRAY: return toObjectArray( val );
5555

@@ -755,6 +755,33 @@ public static List<Boolean> toBooleanArray(Object val) {
755755
}
756756
}
757757

758+
/**
759+
* Convert value to Date array (List&lt;Date&gt;)
760+
*/
761+
public static List<Date> toDateArray(Object val) {
762+
if (val == null) return null;
763+
764+
if (val instanceof List<?>) {
765+
List<?> list = (List<?>) val;
766+
return list.stream()
767+
.map(DataConverter::toDate)
768+
.collect(java.util.stream.Collectors.toList());
769+
} else if (val instanceof String) {
770+
String s = (String) val;
771+
if (s.trim().isEmpty()) return new java.util.ArrayList<>();
772+
773+
if (s.contains(",")) {
774+
return java.util.Arrays.stream(s.split(","))
775+
.map(item -> toDate(item.trim()))
776+
.collect(java.util.stream.Collectors.toList());
777+
} else {
778+
return java.util.Arrays.asList(toDate(s.trim()));
779+
}
780+
} else {
781+
return java.util.Arrays.asList(toDate(val));
782+
}
783+
}
784+
758785
/**
759786
* Convert value to Double array (List&lt;Double&gt;)
760787
*/

0 commit comments

Comments
 (0)