@@ -440,6 +440,142 @@ public StaticTransientData() {}
440440 public StaticTransientData (String s , int v ) { status = s ; value = v ; }
441441 }
442442
443+ // ── Large-payload field filtering (127-field POJO) ─────────────────
444+ //
445+ // Tests prove that ?fields= can select a small subset from a wide
446+ // response object (127 fields) without breaking serialization or
447+ // losing data. Uses programmatically generated generic field names.
448+
449+ /**
450+ * POJO with 127 fields to exercise field filtering at scale.
451+ * Uses mixed types (String, double, int, long, boolean) across
452+ * all 127 fields to verify that filtering handles every
453+ * primitive and object type correctly.
454+ */
455+ public static class WideRecord {
456+ // 30 String fields
457+ public String s0 , s1 , s2 , s3 , s4 , s5 , s6 , s7 , s8 , s9 ;
458+ public String s10 , s11 , s12 , s13 , s14 , s15 , s16 , s17 , s18 , s19 ;
459+ public String s20 , s21 , s22 , s23 , s24 , s25 , s26 , s27 , s28 , s29 ;
460+ // 40 double fields
461+ public double d0 , d1 , d2 , d3 , d4 , d5 , d6 , d7 , d8 , d9 ;
462+ public double d10 , d11 , d12 , d13 , d14 , d15 , d16 , d17 , d18 , d19 ;
463+ public double d20 , d21 , d22 , d23 , d24 , d25 , d26 , d27 , d28 , d29 ;
464+ public double d30 , d31 , d32 , d33 , d34 , d35 , d36 , d37 , d38 , d39 ;
465+ // 25 int fields
466+ public int i0 , i1 , i2 , i3 , i4 , i5 , i6 , i7 , i8 , i9 ;
467+ public int i10 , i11 , i12 , i13 , i14 , i15 , i16 , i17 , i18 , i19 ;
468+ public int i20 , i21 , i22 , i23 , i24 ;
469+ // 20 long fields
470+ public long l0 , l1 , l2 , l3 , l4 , l5 , l6 , l7 , l8 , l9 ;
471+ public long l10 , l11 , l12 , l13 , l14 , l15 , l16 , l17 , l18 , l19 ;
472+ // 12 boolean fields — total: 30+40+25+20+12 = 127
473+ public boolean b0 , b1 , b2 , b3 , b4 , b5 , b6 , b7 , b8 , b9 ;
474+ public boolean b10 , b11 ;
475+
476+ public WideRecord () {}
477+
478+ /** Populate all 127 fields with non-default values. */
479+ public static WideRecord createTestRecord () {
480+ WideRecord r = new WideRecord ();
481+ for (int n = 0 ; n < 30 ; n ++) {
482+ try {
483+ r .getClass ().getField ("s" + n ).set (r , "val_" + n );
484+ } catch (Exception e ) { throw new RuntimeException (e ); }
485+ }
486+ for (int n = 0 ; n < 40 ; n ++) {
487+ try {
488+ r .getClass ().getField ("d" + n ).setDouble (r , n * 1.1 );
489+ } catch (Exception e ) { throw new RuntimeException (e ); }
490+ }
491+ for (int n = 0 ; n < 25 ; n ++) {
492+ try {
493+ r .getClass ().getField ("i" + n ).setInt (r , n * 100 );
494+ } catch (Exception e ) { throw new RuntimeException (e ); }
495+ }
496+ for (int n = 0 ; n < 20 ; n ++) {
497+ try {
498+ r .getClass ().getField ("l" + n ).setLong (r , n * 1000000L );
499+ } catch (Exception e ) { throw new RuntimeException (e ); }
500+ }
501+ for (int n = 0 ; n < 12 ; n ++) {
502+ try {
503+ r .getClass ().getField ("b" + n ).setBoolean (r , n % 2 == 0 );
504+ } catch (Exception e ) { throw new RuntimeException (e ); }
505+ }
506+ return r ;
507+ }
508+ }
509+
510+ @ Test
511+ public void testFilter127FieldsKeepOne () throws Exception {
512+ setReturnObject (WideRecord .createTestRecord ());
513+ outMsgContext .setProperty (JsonConstant .FIELD_FILTER ,
514+ setOf ("s0" ));
515+
516+ formatter .writeTo (outMsgContext , outputFormat , outputStream , false );
517+ JsonElement response = parseResponse ();
518+
519+ Assert .assertEquals ("Should have exactly 1 field" , 1 ,
520+ response .getAsJsonObject ().size ());
521+ Assert .assertEquals ("val_0" ,
522+ response .getAsJsonObject ().get ("s0" ).getAsString ());
523+ }
524+
525+ @ Test
526+ public void testFilter127FieldsKeepFive () throws Exception {
527+ setReturnObject (WideRecord .createTestRecord ());
528+ outMsgContext .setProperty (JsonConstant .FIELD_FILTER ,
529+ setOf ("s0" , "d5" , "i10" , "l15" , "b0" ));
530+
531+ formatter .writeTo (outMsgContext , outputFormat , outputStream , false );
532+ JsonElement response = parseResponse ();
533+
534+ Assert .assertEquals ("Should have exactly 5 fields" , 5 ,
535+ response .getAsJsonObject ().size ());
536+ Assert .assertEquals ("val_0" ,
537+ response .getAsJsonObject ().get ("s0" ).getAsString ());
538+ Assert .assertEquals (5.5 ,
539+ response .getAsJsonObject ().get ("d5" ).getAsDouble (), 0.0001 );
540+ Assert .assertEquals (1000 ,
541+ response .getAsJsonObject ().get ("i10" ).getAsInt ());
542+ Assert .assertEquals (15000000L ,
543+ response .getAsJsonObject ().get ("l15" ).getAsLong ());
544+ Assert .assertTrue (
545+ response .getAsJsonObject ().get ("b0" ).getAsBoolean ());
546+ }
547+
548+ @ Test
549+ public void testFilter127FieldsPayloadSizeReduction () throws Exception {
550+ WideRecord record = WideRecord .createTestRecord ();
551+
552+ // Full response (all 127 fields)
553+ setReturnObject (record );
554+ formatter .writeTo (outMsgContext , outputFormat , outputStream , false );
555+ int fullSize = outputStream .size ();
556+
557+ // Filtered response (1 field out of 127)
558+ outputStream .reset ();
559+ outMsgContext .setProperty (JsonConstant .FIELD_FILTER ,
560+ setOf ("s0" ));
561+ formatter .writeTo (outMsgContext , outputFormat , outputStream , false );
562+ int filteredSize = outputStream .size ();
563+
564+ // The filtered response should be dramatically smaller
565+ double reductionPct = (1.0 - (double ) filteredSize / fullSize ) * 100 ;
566+
567+ Assert .assertTrue (
568+ "Full response (" + fullSize + " bytes) should be > 1KB" ,
569+ fullSize > 1000 );
570+ Assert .assertTrue (
571+ "Filtered response (" + filteredSize + " bytes) should be < 200 bytes" ,
572+ filteredSize < 200 );
573+ Assert .assertTrue (
574+ "Payload reduction (" + String .format ("%.0f" , reductionPct )
575+ + "%) should exceed 90%" ,
576+ reductionPct > 90.0 );
577+ }
578+
443579 static class TestHelper {
444580 static org .apache .axiom .om .OMElement createFaultElement () {
445581 var factory = OMAbstractFactory .getOMFactory ();
0 commit comments