@@ -202,11 +202,22 @@ void pushChildFrames(Frame frame, java.util.Deque<Frame> stack) {
202202 }
203203 case JtdSchema .PropertiesSchema propsSchema -> {
204204 if (instance instanceof JsonObject obj ) {
205- // Push required properties that are present
205+ String discriminatorKey = frame .discriminatorKey ();
206+
207+ // ================================= CHANGE 1: SKIP DISCRIMINATOR FIELD =================================
208+ // ADDED: Skip the discriminator field when pushing required property validation frames
209+ // Push required properties that are present (except discriminator field)
206210 for (var entry : propsSchema .properties ().entrySet ()) {
207211 String key = entry .getKey ();
212+
213+ // Skip the discriminator field - it was already validated by discriminator logic
214+ if (discriminatorKey != null && key .equals (discriminatorKey )) {
215+ LOG .finer (() -> "Skipping discriminator field validation for: " + key );
216+ continue ;
217+ }
218+
208219 JsonValue value = obj .members ().get (key );
209-
220+
210221 if (value != null ) {
211222 String childPtr = frame .ptr + "/" + key ;
212223 Crumbs childCrumbs = frame .crumbs .withObjectField (key );
@@ -215,13 +226,21 @@ void pushChildFrames(Frame frame, java.util.Deque<Frame> stack) {
215226 LOG .finer (() -> "Pushed required property frame at " + childPtr );
216227 }
217228 }
218-
219- // Push optional properties that are present
229+
230+ // ADDED: Skip the discriminator field when pushing optional property validation frames
231+ // Push optional properties that are present (except discriminator field)
220232 for (var entry : propsSchema .optionalProperties ().entrySet ()) {
221233 String key = entry .getKey ();
234+
235+ // Skip the discriminator field - it was already validated by discriminator logic
236+ if (discriminatorKey != null && key .equals (discriminatorKey )) {
237+ LOG .finer (() -> "Skipping discriminator field validation for optional: " + key );
238+ continue ;
239+ }
240+
222241 JtdSchema childSchema = entry .getValue ();
223242 JsonValue value = obj .members ().get (key );
224-
243+
225244 if (value != null ) {
226245 String childPtr = frame .ptr + "/" + key ;
227246 Crumbs childCrumbs = frame .crumbs .withObjectField (key );
@@ -230,6 +249,8 @@ void pushChildFrames(Frame frame, java.util.Deque<Frame> stack) {
230249 LOG .finer (() -> "Pushed optional property frame at " + childPtr );
231250 }
232251 }
252+
253+ // ============================= END CHANGE 1: SKIP DISCRIMINATOR FIELD =============================
233254 }
234255 }
235256 case JtdSchema .ValuesSchema valuesSchema -> {
@@ -252,15 +273,24 @@ void pushChildFrames(Frame frame, java.util.Deque<Frame> stack) {
252273 String discriminatorValueStr = discStr .value ();
253274 JtdSchema variantSchema = discSchema .mapping ().get (discriminatorValueStr );
254275 if (variantSchema != null ) {
255- // Special-case: skip pushing variant schema if object contains only discriminator key
256- if (obj .members ().size () == 1 && obj .members ().containsKey (discSchema .discriminator ())) {
257- LOG .finer (() -> "Skipping variant schema push for discriminator-only object" );
258- } else {
259- // Push variant schema for validation with discriminator key context
260- Frame variantFrame = new Frame (variantSchema , instance , frame .ptr , frame .crumbs , discSchema .discriminator ());
261- stack .push (variantFrame );
262- LOG .finer (() -> "Pushed discriminator variant frame for " + discriminatorValueStr + " with discriminator key: " + discSchema .discriminator ());
263- }
276+
277+ // ========================== CHANGE 2: REMOVE FAULTY OPTIMIZATION ==========================
278+ // REMOVED: Special-case optimization that skipped validation for discriminator-only objects
279+ // OLD CODE:
280+ // if (obj.members().size() == 1 && obj.members().containsKey(discSchema.discriminator())) {
281+ // LOG.finer(() -> "Skipping variant schema push for discriminator-only object");
282+ // } else {
283+ // Frame variantFrame = new Frame(variantSchema, instance, frame.ptr, frame.crumbs, discSchema.discriminator());
284+ // stack.push(variantFrame);
285+ // LOG.finer(() -> "Pushed discriminator variant frame for " + discriminatorValueStr + " with discriminator key: " + discSchema.discriminator());
286+ // }
287+
288+ // NEW CODE: Always push variant schema for validation with discriminator key context
289+ Frame variantFrame = new Frame (variantSchema , instance , frame .ptr , frame .crumbs , discSchema .discriminator ());
290+ stack .push (variantFrame );
291+ LOG .finer (() -> "Pushed discriminator variant frame for " + discriminatorValueStr + " with discriminator key: " + discSchema .discriminator ());
292+ // ======================== END CHANGE 2: REMOVE FAULTY OPTIMIZATION ========================
293+
264294 }
265295 }
266296 }
@@ -342,7 +372,7 @@ JtdSchema compileObjectSchema(JsonObject obj) {
342372
343373 // RFC 8927: {} is the empty form and accepts all instances
344374 if (forms .isEmpty () && obj .members ().isEmpty ()) {
345- LOG .info (() -> "Empty schema {} encountered. Per RFC 8927 this means 'accept anything'. "
375+ LOG .finer (() -> "Empty schema {} encountered. Per RFC 8927 this means 'accept anything'. "
346376 + "Some non-JTD validators interpret {} with object semantics; this implementation follows RFC 8927." );
347377 return new JtdSchema .EmptySchema ();
348378 } else if (forms .isEmpty ()) {
@@ -352,7 +382,7 @@ JtdSchema compileObjectSchema(JsonObject obj) {
352382
353383 if (!hasNonMetadataKeys ) {
354384 // This is an empty schema (possibly with metadata)
355- LOG .info (() -> "Empty schema encountered (with metadata: " + members .keySet () + "). "
385+ LOG .finer (() -> "Empty schema encountered (with metadata: " + members .keySet () + "). "
356386 + "Per RFC 8927 this means 'accept anything'. "
357387 + "Some non-JTD validators interpret {} with object semantics; this implementation follows RFC 8927." );
358388 return new JtdSchema .EmptySchema ();
0 commit comments