@@ -193,17 +193,14 @@ public <T> T getFieldValue(String path, Class<T> clazz) {
193193 public <T > T getFieldValue (String path , Class <T > clazz , boolean ignoreMissing ) {
194194 final FieldPath fieldPath = FieldPath .of (path );
195195 Object context = fieldPath .initialContext (this );
196- for (String pathElement : fieldPath .pathElements ) {
197- ResolveResult result = resolve (pathElement , path , context );
198- if (result .wasSuccessful ) {
199- context = result .resolvedObject ;
200- } else if (ignoreMissing && hasField (path ) == false ) {
201- return null ;
202- } else {
203- throw new IllegalArgumentException (result .errorMessage );
204- }
196+ ResolveResult result = resolve (fieldPath .pathElements , fieldPath .pathElements .length , path , context );
197+ if (result .wasSuccessful ) {
198+ return cast (path , result .resolvedObject , clazz );
199+ } else if (ignoreMissing ) {
200+ return null ;
201+ } else {
202+ throw new IllegalArgumentException (result .errorMessage );
205203 }
206- return cast (path , context , clazz );
207204 }
208205
209206 /**
@@ -266,6 +263,8 @@ public boolean hasField(String path, boolean failOutOfRange) {
266263 String pathElement = fieldPath .pathElements [i ];
267264 if (context == null ) {
268265 return false ;
266+ } else if (context instanceof IngestCtxMap map ) { // optimization: handle IngestCtxMap separately from Map
267+ context = map .get (pathElement );
269268 } else if (context instanceof Map <?, ?> map ) {
270269 context = map .get (pathElement );
271270 } else if (context instanceof List <?> list ) {
@@ -292,6 +291,8 @@ public boolean hasField(String path, boolean failOutOfRange) {
292291 String leafKey = fieldPath .pathElements [fieldPath .pathElements .length - 1 ];
293292 if (context == null ) {
294293 return false ;
294+ } else if (context instanceof IngestCtxMap map ) { // optimization: handle IngestCtxMap separately from Map
295+ return map .containsKey (leafKey );
295296 } else if (context instanceof Map <?, ?> map ) {
296297 return map .containsKey (leafKey );
297298 } else if (context instanceof List <?> list ) {
@@ -322,18 +323,22 @@ public boolean hasField(String path, boolean failOutOfRange) {
322323 public void removeField (String path ) {
323324 final FieldPath fieldPath = FieldPath .of (path );
324325 Object context = fieldPath .initialContext (this );
325- for (int i = 0 ; i < fieldPath .pathElements .length - 1 ; i ++) {
326- ResolveResult result = resolve (fieldPath .pathElements [i ], path , context );
327- if (result .wasSuccessful ) {
328- context = result .resolvedObject ;
329- } else {
330- throw new IllegalArgumentException (result .errorMessage );
331- }
326+ ResolveResult result = resolve (fieldPath .pathElements , fieldPath .pathElements .length - 1 , path , context );
327+ if (result .wasSuccessful ) {
328+ context = result .resolvedObject ;
329+ } else {
330+ throw new IllegalArgumentException (result .errorMessage );
332331 }
333332
334333 String leafKey = fieldPath .pathElements [fieldPath .pathElements .length - 1 ];
335334 if (context == null ) {
336335 throw new IllegalArgumentException (Errors .cannotRemove (path , leafKey , null ));
336+ } else if (context instanceof IngestCtxMap map ) { // optimization: handle IngestCtxMap separately from Map
337+ if (map .containsKey (leafKey )) {
338+ map .remove (leafKey );
339+ } else {
340+ throw new IllegalArgumentException (Errors .notPresent (path , leafKey ));
341+ }
337342 } else if (context instanceof Map <?, ?> map ) {
338343 if (map .containsKey (leafKey )) {
339344 map .remove (leafKey );
@@ -357,33 +362,48 @@ public void removeField(String path) {
357362 }
358363 }
359364
360- private static ResolveResult resolve (String pathElement , String fullPath , Object context ) {
361- if (context == null ) {
362- return ResolveResult .error (Errors .cannotResolve (fullPath , pathElement , null ));
363- } else if (context instanceof Map <?, ?>) {
364- @ SuppressWarnings ("unchecked" )
365- Map <String , Object > map = (Map <String , Object >) context ;
366- Object object = map .getOrDefault (pathElement , NOT_FOUND ); // getOrDefault is faster than containsKey + get
367- if (object == NOT_FOUND ) {
368- return ResolveResult .error (Errors .notPresent (fullPath , pathElement ));
369- } else {
370- return ResolveResult .success (object );
371- }
372- } else if (context instanceof List <?> list ) {
373- int index ;
374- try {
375- index = Integer .parseInt (pathElement );
376- } catch (NumberFormatException e ) {
377- return ResolveResult .error (Errors .notInteger (fullPath , pathElement ));
378- }
379- if (index < 0 || index >= list .size ()) {
380- return ResolveResult .error (Errors .outOfBounds (fullPath , index , list .size ()));
365+ /**
366+ * Resolves the path elements (up to the limit) within the context. The result of such resolution can either be successful,
367+ * or can indicate a failure.
368+ */
369+ private static ResolveResult resolve (final String [] pathElements , final int limit , final String fullPath , Object context ) {
370+ for (int i = 0 ; i < limit ; i ++) {
371+ String pathElement = pathElements [i ];
372+ if (context == null ) {
373+ return ResolveResult .error (Errors .cannotResolve (fullPath , pathElement , null ));
374+ } else if (context instanceof IngestCtxMap map ) { // optimization: handle IngestCtxMap separately from Map
375+ Object object = map .getOrDefault (pathElement , NOT_FOUND ); // getOrDefault is faster than containsKey + get
376+ if (object == NOT_FOUND ) {
377+ return ResolveResult .error (Errors .notPresent (fullPath , pathElement ));
378+ } else {
379+ context = object ;
380+ }
381+ } else if (context instanceof Map <?, ?>) {
382+ @ SuppressWarnings ("unchecked" )
383+ Map <String , Object > map = (Map <String , Object >) context ;
384+ Object object = map .getOrDefault (pathElement , NOT_FOUND ); // getOrDefault is faster than containsKey + get
385+ if (object == NOT_FOUND ) {
386+ return ResolveResult .error (Errors .notPresent (fullPath , pathElement ));
387+ } else {
388+ context = object ;
389+ }
390+ } else if (context instanceof List <?> list ) {
391+ int index ;
392+ try {
393+ index = Integer .parseInt (pathElement );
394+ } catch (NumberFormatException e ) {
395+ return ResolveResult .error (Errors .notInteger (fullPath , pathElement ));
396+ }
397+ if (index < 0 || index >= list .size ()) {
398+ return ResolveResult .error (Errors .outOfBounds (fullPath , index , list .size ()));
399+ } else {
400+ context = list .get (index );
401+ }
381402 } else {
382- return ResolveResult .success ( list . get ( index ));
403+ return ResolveResult .error ( Errors . cannotResolve ( fullPath , pathElement , context ));
383404 }
384- } else {
385- return ResolveResult .error (Errors .cannotResolve (fullPath , pathElement , context ));
386405 }
406+ return ResolveResult .success (context );
387407 }
388408
389409 /**
@@ -518,6 +538,15 @@ private void setFieldValue(String path, Object value, boolean append, boolean al
518538 String pathElement = fieldPath .pathElements [i ];
519539 if (context == null ) {
520540 throw new IllegalArgumentException (Errors .cannotResolve (path , pathElement , null ));
541+ } else if (context instanceof IngestCtxMap map ) { // optimization: handle IngestCtxMap separately from Map
542+ Object object = map .getOrDefault (pathElement , NOT_FOUND ); // getOrDefault is faster than containsKey + get
543+ if (object == NOT_FOUND ) {
544+ Map <Object , Object > newMap = new HashMap <>();
545+ map .put (pathElement , newMap );
546+ context = newMap ;
547+ } else {
548+ context = object ;
549+ }
521550 } else if (context instanceof Map <?, ?>) {
522551 @ SuppressWarnings ("unchecked" )
523552 Map <String , Object > map = (Map <String , Object >) context ;
@@ -549,6 +578,22 @@ private void setFieldValue(String path, Object value, boolean append, boolean al
549578 String leafKey = fieldPath .pathElements [fieldPath .pathElements .length - 1 ];
550579 if (context == null ) {
551580 throw new IllegalArgumentException (Errors .cannotSet (path , leafKey , null ));
581+ } else if (context instanceof IngestCtxMap map ) { // optimization: handle IngestCtxMap separately from Map
582+ if (append ) {
583+ Object object = map .getOrDefault (leafKey , NOT_FOUND ); // getOrDefault is faster than containsKey + get
584+ if (object == NOT_FOUND ) {
585+ List <Object > list = new ArrayList <>();
586+ appendValues (list , value );
587+ map .put (leafKey , list );
588+ } else {
589+ Object list = appendValues (object , value , allowDuplicates );
590+ if (list != object ) {
591+ map .put (leafKey , list );
592+ }
593+ }
594+ return ;
595+ }
596+ map .put (leafKey , value );
552597 } else if (context instanceof Map <?, ?>) {
553598 @ SuppressWarnings ("unchecked" )
554599 Map <String , Object > map = (Map <String , Object >) context ;
0 commit comments