• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

burningwave / json / #12

04 Apr 2025 12:14PM UTC coverage: 45.488% (-0.3%) from 45.756%
#12

push

Roberto-Gentili
Releasing new version

373 of 820 relevant lines covered (45.49%)

0.45 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

66.07
/src/main/java/org/burningwave/json/ObjectHandler.java
1
/*
2
 * This file is part of Burningwave JSON.
3
 *
4
 * Author: Roberto Gentili
5
 *
6
 * Hosted at: https://github.com/burningwave/json
7
 *
8
 * --
9
 *
10
 * The MIT License (MIT)
11
 *
12
 * Copyright (c) 2023 Roberto Gentili
13
 *
14
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
15
 * documentation files (the "Software"), to deal in the Software without restriction, including without
16
 * limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
17
 * the Software, and to permit persons to whom the Software is furnished to do so, subject to the following
18
 * conditions:
19
 *
20
 * The above copyright notice and this permission notice shall be included in all copies or substantial
21
 * portions of the Software.
22
 *
23
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
24
 * LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO
25
 * EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
26
 * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
27
 * OR OTHER DEALINGS IN THE SOFTWARE.
28
 */
29
package org.burningwave.json;
30

31
import java.util.AbstractMap;
32
import java.util.ArrayList;
33
import java.util.Collection;
34
import java.util.Collections;
35
import java.util.LinkedHashMap;
36
import java.util.List;
37
import java.util.Map;
38
import java.util.Optional;
39
import java.util.concurrent.atomic.AtomicInteger;
40
import java.util.function.Function;
41
import java.util.function.Predicate;
42
import java.util.function.Supplier;
43
import java.util.regex.Matcher;
44
import java.util.regex.Pattern;
45
import java.util.stream.Collectors;
46
import java.util.stream.Stream;
47

48
import org.burningwave.Classes;
49
import org.burningwave.TerminateIterationException;
50
import org.burningwave.Throwables;
51

52
import com.fasterxml.jackson.core.JsonProcessingException;
53
import com.fasterxml.jackson.core.type.TypeReference;
54
import com.fasterxml.jackson.databind.ObjectMapper;
55

56
@SuppressWarnings("unchecked")
57
public class ObjectHandler  {
58

59
        private final static Pattern INDEXES_SEARCHER_FOR_INDEXED_FIELD;
60

61
        private static Function<ObjectHandler, Object> valueRetriever;
62
        static {
63
                try {
64
                        if ((boolean)Configuration.freezeAndGet().get(Configuration.Key.REFLECTION_ENABLED)) {
1✔
65
                                valueRetriever = buildValueRetriever(org.burningwave.reflection.FieldAccessor.INSTANCE);
1✔
66
                        }
67
                } catch (Throwable exc) {
×
68

69
                } finally {
70
                        if (valueRetriever == null) {
1✔
71
                                valueRetriever = buildAlwaysNullVaueIfNotValorizedRetriever();
×
72
                        }
73
                }
×
74
                INDEXES_SEARCHER_FOR_INDEXED_FIELD = Pattern.compile("\\[(.*?)\\]");
1✔
75
        }
1✔
76

77
        static Function<ObjectHandler, Object> buildValueRetriever(
78
                org.burningwave.reflection.FieldAccessor fieldAccessor
79
        ) {
80
                return objectHandler -> {
1✔
81
                        Object value = objectHandler.rootHandler.getValue();
1✔
82
                        for (String pathSegment : objectHandler.removeRootPrefix(objectHandler.path).split("\\.")) {
1✔
83
                                if (value == null) {
1✔
84
                                        break;
×
85
                                }
86
                                if (value instanceof Map) {
1✔
87
                                        Matcher matcher = INDEXES_SEARCHER_FOR_INDEXED_FIELD.matcher(pathSegment);
×
88
                                        String index = "";
×
89
                                        if (matcher.find()) {
×
90
                                                index = matcher.group(0);
×
91
                                        }
92
                                        if (!index.isEmpty()) {
×
93
                                                pathSegment = pathSegment.replace(index, "");
×
94
                                        }
95
                                        pathSegment = "[" + pathSegment + "]" + index;
×
96
                                }
97
                                try {
98
                                        value = fieldAccessor.get(value, pathSegment);
1✔
99
                                } catch (IndexOutOfBoundsException exc) {
×
100
                                        value = null;
×
101
                                        break;
×
102
                                }
1✔
103
                        }
104
                        return value;
1✔
105
                };
106
        }
107

108
        static Function<ObjectHandler, Object> buildAlwaysNullVaueIfNotValorizedRetriever() {
109
                return objectHandler ->
×
110
                        objectHandler.rootHandler.rawValue == objectHandler.rootHandler.getValue() ?
×
111
                                objectHandler.rawValue : null;
112
        }
113

114
        final ObjectMapper objectMapper;
115
        final ObjectHandler rootHandler;
116
        final String path;
117
        Supplier<Object> valueSupplier;
118
        final Object rawValue;
119

120
        ObjectHandler (
121
                ObjectMapper objectMapper,
122
                String path,
123
                ObjectHandler masterObjectHandler,
124
                Object convertedValue,
125
                Object originalValue
126
        ) {
1✔
127
                this.objectMapper = objectMapper;
1✔
128
                this.path = path;
1✔
129
                if (masterObjectHandler == null && Path.Segment.root.equals(path)) {
1✔
130
                        this.rootHandler = this;
1✔
131
                } else {
132
                        this.rootHandler = masterObjectHandler;
1✔
133
                }
134
                this.rawValue = convertedValue;
1✔
135
                if (originalValue != null) {
1✔
136
                        this.valueSupplier = () -> originalValue;
1✔
137
                } else {
138
                        this.valueSupplier = () -> valueRetriever.apply(this);
1✔
139
                }
140
        }
1✔
141

142
        public static ObjectHandler create(
143
                ObjectMapper objectMapper,
144
                Object jsonObject
145
        ) {
146
                if (isConvertible(jsonObject)) {
1✔
147
                        if (jsonObject instanceof Collection) {
1✔
148
                                return new ObjectHandler(
×
149
                                        objectMapper,
150
                                        Path.Segment.root,
151
                                        null,
152
                                        objectMapper.convertValue(jsonObject, new TypeReference<List<Object>>(){}),
×
153
                                        jsonObject
154
                                );
155
                        } else {
156
                                return new ObjectHandler(
1✔
157
                                        objectMapper,
158
                                        Path.Segment.root,
159
                                        null,
160
                                        objectMapper.convertValue(jsonObject, new TypeReference<Map<String, Object>>(){}),
1✔
161
                                        jsonObject
162
                                );
163
                        }
164
                }
165
                return new ObjectHandler(objectMapper, Path.Segment.root, null, jsonObject, jsonObject);
×
166
        }
167

168
        static boolean isConvertible(Object jsonObject) {
169
                return !(
1✔
170
                        Classes.INSTANCE.isPrimitive(jsonObject) ||
1✔
171
                        jsonObject instanceof Map ||
172
                        (jsonObject instanceof Collection && ((Collection<?>)jsonObject).stream().findFirst().map(Map.class::isInstance).orElseGet(() -> false))
1✔
173
                );
174
        }
175

176
        public Finder newFinder() {
177
                return new Finder(this);
1✔
178
        }
179

180
        public ValueFinder newValueFinder() {
181
                return new ValueFinder(this);
1✔
182
        }
183

184
        public ValueFinderAndConverter newValueFinderAndConverter(Class<?> outputClass) {
185
                return new ValueFinderAndConverter(this, outputClass);
1✔
186
        }
187

188
        protected <O> O findFirst(
189
                Object jSonObject,
190
                Predicate<ObjectHandler> filter,
191
                Function<ObjectHandler, O> converter
192
        ) {
193
                List<O> collector = new ArrayList<>();
1✔
194
                try {
195
                        findValues(
1✔
196
                                isRoot() ?
1✔
197
                                        Path.Segment.root : "",
198
                                jSonObject,
199
                                objectHandler -> {
200
                                        boolean testResult = objectHandler.rawValue != null && filter.test(objectHandler);
1✔
201
                                        return new AbstractMap.SimpleEntry<>(testResult, testResult ? TerminateIterationException.INSTANCE : null);
1✔
202
                                },
203
                                converter,
204
                                collector
205
                        );
206
                } catch (TerminateIterationException exc) {
1✔
207

208
                }
×
209
                return collector.stream().findFirst().orElseGet(() -> null);
1✔
210
        }
211

212
        protected <O> List<O> find(
213
                Object jSonObject,
214
                Predicate<ObjectHandler> filter,
215
                Function<ObjectHandler, O> converter
216
        ) {
217
                List<O> collector = new ArrayList<>();
×
218
                findValues(
×
219
                        isRoot() ?
×
220
                                Path.Segment.root : "",
221
                        jSonObject,
222
                        objectHandler ->
223
                                new AbstractMap.SimpleEntry<>(filter.test(objectHandler), null),
×
224
                        converter,
225
                        collector
226
                );
227
                return collector;
×
228
        }
229

230
        protected <I, O> void findValues(
231
                String path,
232
                Object jSonObject,
233
                Function<ObjectHandler, Map.Entry<Boolean, TerminateIterationException>> filter,
234
                Function<ObjectHandler, O> converter,
235
                Collection<O> collector
236
        ) {
237
                if (jSonObject instanceof Map) {
1✔
238
                        findValues(
1✔
239
                                path,
240
                                (Map<String, Object>)jSonObject,
241
                                filter,
242
                                converter,
243
                                collector
244
                        );
245
                } else if (jSonObject instanceof Collection) {
1✔
246
                        findValues(
1✔
247
                                path,
248
                                (Collection<I>)jSonObject,
249
                                filter,
250
                                converter,
251
                                collector
252
                        );
253
                } else {
254
                        checkAndCollectValue(
1✔
255
                                path,
256
                                jSonObject,
257
                                filter,
258
                                converter,
259
                                collector
260
                        );
261
                }
262
        }
1✔
263

264
        protected <O> void findValues(
265
                String path,
266
                Map<String, Object> jSonObject,
267
                Function<ObjectHandler, Map.Entry<Boolean, TerminateIterationException>> filter,
268
                Function<ObjectHandler, O> converter,
269
                Collection<O> collector
270
        ) {
271
                checkAndCollectValue(path, jSonObject, filter, converter, collector);
1✔
272
                for (Map.Entry<String, Object> nameAndValue : jSonObject.entrySet()) {
1✔
273
                        String iteratedPath = (!path.isEmpty() ?
1✔
274
                                path + "." :
275
                                path
276
                        ) + nameAndValue.getKey();
1✔
277
                        findValues(
1✔
278
                                iteratedPath,
279
                                nameAndValue.getValue(),
1✔
280
                                filter,
281
                                converter,
282
                                collector
283
                        );
284
                }
1✔
285
        }
1✔
286

287
        protected <I, O> void findValues(
288
                String path,
289
                Collection<I> jSonObject,
290
                Function<ObjectHandler, Map.Entry<Boolean, TerminateIterationException>> filter,
291
                Function<ObjectHandler, O> converter,
292
                Collection<O> collector
293
        ) {
294
                checkAndCollectValue(path, jSonObject, filter, converter, collector);
1✔
295
                Stream<I> indexedObjectStream = jSonObject != null?
1✔
296
                        (jSonObject).stream():
1✔
297
                        Stream.of();
1✔
298
                AtomicInteger index = new AtomicInteger(0);
1✔
299
                indexedObjectStream.forEach(vl -> findValues(
1✔
300
                        path + "[" + index.getAndIncrement() + "]",
1✔
301
                        vl,
302
                        filter,
303
                        converter,
304
                        collector
305
                ));
306
        }
1✔
307

308
        <O> void checkAndCollectValue(
309
                String path,
310
                Object value,
311
                Function<ObjectHandler, Map.Entry<Boolean, TerminateIterationException>> filter,
312
                Function<ObjectHandler, O> converter,
313
                Collection<O> collector
314
        ) {
315
                path = removeRootPrefix(path);
1✔
316
                String finalPath = this.path.isEmpty() ? path :
1✔
317
                        path.isEmpty() ?
1✔
318
                                this.path :
319
                                this.path + "." + path;
320
                ObjectHandler objectHandler = !Path.INSTANCE.isRoot(finalPath) ?
1✔
321
                        new ObjectHandler(
322
                                objectMapper,
323
                                finalPath,
324
                                rootHandler,
325
                                value,
326
                                null
327
                        ) :
328
                        rootHandler;
329
                Map.Entry<Boolean, TerminateIterationException> testResult = filter.apply(objectHandler);
1✔
330
                if (testResult.getKey().booleanValue()) {
1✔
331
                        collector.add(converter.apply(objectHandler));
1✔
332
                }
333
                TerminateIterationException terminateIterationException = testResult.getValue();
1✔
334
                if (terminateIterationException != null) {
1✔
335
                        throw terminateIterationException;
1✔
336
                }
337
        }
1✔
338

339
        public <T> T getRawValue() {
340
                return (T)rawValue;
1✔
341
        }
342

343
        public <T> T getValue() {
344
                return (T)valueSupplier.get();
1✔
345
        }
346

347
        public <T> T getValueOrRawValue() {
348
                T value = getValue();
1✔
349
                return value != null ? value : getRawValue();
1✔
350
        }
351

352
        protected String removeRootPrefix(String path) {
353
                if (!Path.Segment.root.isEmpty() && path.startsWith(Path.Segment.root)) {
1✔
354
                        path = path.replaceFirst(Path.Segment.root, "");
×
355
                        if (path.startsWith(".")) {
×
356
                                path = path.substring(1);
×
357
                        }
358
                }
359
                return path;
1✔
360
        }
361

362
        public String getPath() {
363
                return this.path;
1✔
364
        }
365

366
        public ObjectHandler getParent() {
367
                try {
368
                        return rootHandler.newFinder().findForPathEquals(Path.INSTANCE.normalize(this.path, Path.Segment.parent));
×
369
                } catch (IllegalArgumentException exc) {
×
370
                        return null;
×
371
                }
372
        }
373

374
        public boolean isRoot() {
375
                return Path.INSTANCE.isRoot(path);
1✔
376
        }
377

378
        public <I, T> T convert(Class<T> targetClass) {
379
                I originalValue = getValueOrRawValue();
1✔
380
                if (originalValue == null) {
1✔
381
                        return null;
×
382
                }
383
                return objectMapper.convertValue(originalValue, targetClass);
1✔
384
        }
385

386
        @Override
387
        public String toString() {
388
                return
×
389
                        path + " - " +
390
                        Optional.ofNullable(rawValue).map(this::valueToString).orElseGet(() -> "null");
×
391
        }
392

393
        protected String valueToString(Object value) {
394
                try {
395
                        return objectMapper.writeValueAsString(value);
×
396
                } catch (JsonProcessingException exc) {
×
397
                        return Throwables.INSTANCE.throwException(exc);
×
398
                }
399
        }
400

401
        public static final class Configuration {
402

403
                private Configuration() {}
×
404

405
                private static Map<String, Object> values;
406
                private static boolean freezed;
407

408
                public static final class Key {
409

410
                        private Key() {}
×
411

412
                        private static final String REFLECTION_ENABLED = "reflection.enabled";
413
                }
414

415
                static {
416
                        values = new LinkedHashMap<>();
1✔
417
                        putConfigurationValue(Configuration.Key.REFLECTION_ENABLED, true);
1✔
418
                }
1✔
419

420
                public static void disableReflection() {
421
                        putConfigurationValue(Configuration.Key.REFLECTION_ENABLED, false);
×
422
                }
×
423

424
                private static void putConfigurationValue(String key, Object value) {
425
                        try {
426
                                values.put(key, value);
1✔
427
                        } catch (UnsupportedOperationException exc) {
×
428
                                throw new UnsupportedOperationException("Cannot add configuration value after that the " + ObjectHandler.class.getSimpleName() + " has been initialized");
×
429
                        }
1✔
430
                }
1✔
431

432
                private static Map<String, Object> freezeAndGet() {
433
                        if (!freezed) {
1✔
434
                                values = Collections.unmodifiableMap(Configuration.values);
1✔
435
                        }
436
                        return values;
1✔
437
                }
438

439
        }
440

441
        abstract static class AbstFinder {
442
                private ObjectHandler objectHandler;
443

444
                AbstFinder(ObjectHandler objectHandler) {
1✔
445
                        this.objectHandler = objectHandler;
1✔
446
                }
1✔
447

448
                public <V> List<V> find(Predicate<ObjectHandler> filter) {
449
                        return objectHandler.find(objectHandler.rawValue, filter, this::convert);
×
450
                }
451

452
                public <V> V findFirst(Predicate<ObjectHandler> filter) {
453
                        return objectHandler.findFirst(objectHandler.rawValue, filter, this::convert);
1✔
454
                }
455

456
                public <V> V findForPathEquals(String... pathSegments) {
457
                        return findFirst(oW -> oW.path.equals(Path.of(pathSegments)));
1✔
458
                }
459

460
                public <V> V findFirstForPath(Predicate<String> pathPredicate) {
461
                        return findFirst(oW -> pathPredicate.test(oW.path));
×
462
                }
463

464
                public <V> V findFirstForPathStartsWith(String... pathSegments) {
465
                        return findFirstForPathMatches(Path.INSTANCE.toStartsWithRegEx(Path.of(pathSegments)));
×
466
                }
467

468
                public <V> V findFirstForPathEndsWith(String... pathSegments) {
469
                        return findFirstForPathMatches(Path.INSTANCE.toEndsWithRegEx(Path.of(pathSegments)));
1✔
470
                }
471

472
                public <V> V findFirstForPathContains(String... pathSegments) {
473
                        return findFirstForPathMatches(Path.INSTANCE.toContainsRegEx(Path.of(pathSegments)));
×
474
                }
475

476
                public <V> V findFirstForPathMatches(String regEx) {
477
                        return findFirst(oW -> oW.path.matches(regEx));
1✔
478
                }
479

480
                public <I, V> V findFirstForValue(Predicate<I> valuePredicate) {
481
                        return findFirst(oW -> valuePredicate.test(oW.getValueOrRawValue()));
×
482
                }
483

484
                public <V> List<V> findForPathStartsWith(String... pathSegments) {
485
                        return findForPathMatches(Path.INSTANCE.toStartsWithRegEx(Path.of(pathSegments)));
×
486
                }
487

488
                public <V> List<V> findForPathEndsWith(String... pathSegments) {
489
                        return findForPathMatches(Path.INSTANCE.toEndsWithRegEx(Path.of(pathSegments)));
×
490
                }
491

492
                public <V> List<V> findForPathContains(String... pathSegments) {
493
                        return findForPathMatches(Path.INSTANCE.toContainsRegEx(Path.of(pathSegments)));
×
494
                }
495

496
                public <V> List<V> findForPathMatches(String regEx) {
497
                        return find(oW -> oW.path.matches(regEx));
×
498
                }
499

500
                public <V> List<V> findForPath(Predicate<String> pathPredicate) {
501
                        return find(oW -> pathPredicate.test(oW.path));
×
502
                }
503

504
                public <I, V> List<V> findForValue(Predicate<I> valuePredicate) {
505
                        return find(oW -> valuePredicate.test(oW.getValueOrRawValue()));
×
506
                }
507

508
                <V> List<V> convert(List<ObjectHandler> founds) {
509
                        return (List<V>)founds.stream().map(this::convert).collect(Collectors.toList());
×
510
                }
511

512
                abstract <V> V convert(ObjectHandler found);
513

514
        }
515

516
        public static class Finder extends AbstFinder {
517

518
                Finder(ObjectHandler objectHandler) {
519
                        super(objectHandler);
1✔
520
                }
1✔
521

522
                @Override
523
                ObjectHandler convert(ObjectHandler found) {
524
                        return found;
1✔
525
                }
526

527
        }
528

529
        public static class ValueFinder extends AbstFinder {
530

531
                ValueFinder(ObjectHandler objectHandler) {
532
                        super(objectHandler);
1✔
533
                }
1✔
534

535
                @Override
536
                <V> V convert(ObjectHandler found) {
537
                        return found.getValueOrRawValue();
1✔
538
                }
539

540
        }
541

542
        public static class ValueFinderAndConverter extends AbstFinder {
543

544
                private Class<?> outputClass;
545

546
                ValueFinderAndConverter(ObjectHandler objectHandler, Class<?> outputClass) {
547
                        super(objectHandler);
1✔
548
                        this.outputClass = outputClass;
1✔
549
                }
1✔
550

551
                @Override
552
                <V> V convert(ObjectHandler found) {
553
                        return (V)found.convert(outputClass);
1✔
554
                }
555

556
                public ValueFinderAndConverter changeOutputClass(Class<?> outputClass) {
557
                        this.outputClass = outputClass;
×
558
                        return this;
×
559
                }
560

561
        }
562

563
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc