0
# Type System and Error Handling
1
2
Runtime type checking, safe type conversion, and comprehensive error reporting for invalid input with detailed exception information and type safety utilities.
3
4
## Capabilities
5
6
### Type Checking
7
8
Determine the type of JSON values at runtime with boolean predicates.
9
10
```java { .api }
11
/**
12
* Returns true if this is a JSON object
13
* @return true if this value represents a JSON object
14
*/
15
boolean isObject();
16
17
/**
18
* Returns true if this is a JSON array
19
* @return true if this value represents a JSON array
20
*/
21
boolean isArray();
22
23
/**
24
* Returns true if this is a JSON string
25
* @return true if this value represents a JSON string
26
*/
27
boolean isString();
28
29
/**
30
* Returns true if this is a JSON number
31
* @return true if this value represents a JSON number
32
*/
33
boolean isNumber();
34
35
/**
36
* Returns true if this is a JSON boolean
37
* @return true if this value represents a JSON boolean
38
*/
39
boolean isBoolean();
40
41
/**
42
* Returns true if this is the JSON literal true
43
* @return true if this value represents the JSON literal true
44
*/
45
boolean isTrue();
46
47
/**
48
* Returns true if this is the JSON literal false
49
* @return true if this value represents the JSON literal false
50
*/
51
boolean isFalse();
52
53
/**
54
* Returns true if this is the JSON literal null
55
* @return true if this value represents the JSON literal null
56
*/
57
boolean isNull();
58
59
/**
60
* Returns the type of this JSON value
61
* @return JsonType enum representing the value type
62
*/
63
JsonType getType();
64
```
65
66
**Usage Examples:**
67
68
```java
69
import org.hjson.JsonValue;
70
import org.hjson.JsonType;
71
72
JsonValue value = JsonValue.readHjson("""
73
{
74
name: "John Doe"
75
age: 30
76
active: true
77
spouse: null
78
children: []
79
metadata: {}
80
}
81
""").asObject();
82
83
// Type checking for safe processing
84
for (JsonObject.Member member : value.asObject()) {
85
String name = member.getName();
86
JsonValue memberValue = member.getValue();
87
88
if (memberValue.isString()) {
89
System.out.println(name + " is a string: " + memberValue.asString());
90
} else if (memberValue.isNumber()) {
91
System.out.println(name + " is a number: " + memberValue.asInt());
92
} else if (memberValue.isBoolean()) {
93
System.out.println(name + " is a boolean: " + memberValue.asBoolean());
94
} else if (memberValue.isNull()) {
95
System.out.println(name + " is null");
96
} else if (memberValue.isArray()) {
97
System.out.println(name + " is an array with " + memberValue.asArray().size() + " elements");
98
} else if (memberValue.isObject()) {
99
System.out.println(name + " is an object with " + memberValue.asObject().size() + " members");
100
}
101
102
// Using getType() enum
103
JsonType type = memberValue.getType();
104
System.out.println(name + " type: " + type);
105
}
106
107
// Specific boolean value checking
108
JsonValue boolValue = JsonValue.valueOf(true);
109
if (boolValue.isTrue()) {
110
System.out.println("Value is specifically true");
111
}
112
113
JsonValue falseValue = JsonValue.valueOf(false);
114
if (falseValue.isFalse()) {
115
System.out.println("Value is specifically false");
116
}
117
```
118
119
### Type Conversion
120
121
Convert JSON values to specific Java types with runtime type checking.
122
123
```java { .api }
124
/**
125
* Returns this JSON value as JsonObject
126
* @return this value as JsonObject
127
* @throws UnsupportedOperationException if this value is not a JSON object
128
*/
129
JsonObject asObject();
130
131
/**
132
* Returns this JSON value as JsonArray
133
* @return this value as JsonArray
134
* @throws UnsupportedOperationException if this value is not a JSON array
135
*/
136
JsonArray asArray();
137
138
/**
139
* Returns this JSON value as String
140
* @return string representation of this value
141
* @throws UnsupportedOperationException if this value is not a JSON string
142
*/
143
String asString();
144
145
/**
146
* Returns this JSON value as int
147
* @return int representation of this value
148
* @throws UnsupportedOperationException if this value is not a JSON number
149
* @throws NumberFormatException if the value cannot be represented as int
150
*/
151
int asInt();
152
153
/**
154
* Returns this JSON value as long
155
* @return long representation of this value
156
* @throws UnsupportedOperationException if this value is not a JSON number
157
* @throws NumberFormatException if the value cannot be represented as long
158
*/
159
long asLong();
160
161
/**
162
* Returns this JSON value as float
163
* @return float representation of this value
164
* @throws UnsupportedOperationException if this value is not a JSON number
165
* @throws NumberFormatException if the value cannot be represented as float
166
*/
167
float asFloat();
168
169
/**
170
* Returns this JSON value as double
171
* @return double representation of this value
172
* @throws UnsupportedOperationException if this value is not a JSON number
173
* @throws NumberFormatException if the value cannot be represented as double
174
*/
175
double asDouble();
176
177
/**
178
* Returns this JSON value as boolean
179
* @return boolean representation of this value
180
* @throws UnsupportedOperationException if this value is not a JSON boolean
181
*/
182
boolean asBoolean();
183
184
/**
185
* Returns this JSON value as DSF (Domain Specific Format) object
186
* @return DSF object representation of this value
187
* @throws UnsupportedOperationException if this value is not a DSF value
188
*/
189
Object asDsf();
190
```
191
192
**Usage Examples:**
193
194
```java
195
import org.hjson.JsonValue;
196
import org.hjson.JsonObject;
197
import org.hjson.JsonArray;
198
199
// Safe type conversion with checking
200
JsonValue data = JsonValue.readHjson("""
201
{
202
config: {
203
port: 8080
204
name: "My App"
205
enabled: true
206
}
207
servers: ["server1", "server2", "server3"]
208
version: 1.5
209
}
210
""");
211
212
JsonObject root = data.asObject();
213
214
// Safe object conversion
215
JsonValue configValue = root.get("config");
216
if (configValue.isObject()) {
217
JsonObject config = configValue.asObject();
218
219
// Safe numeric conversion
220
JsonValue portValue = config.get("port");
221
if (portValue.isNumber()) {
222
int port = portValue.asInt();
223
System.out.println("Port: " + port);
224
}
225
226
// Safe string conversion
227
JsonValue nameValue = config.get("name");
228
if (nameValue.isString()) {
229
String name = nameValue.asString();
230
System.out.println("Name: " + name);
231
}
232
233
// Safe boolean conversion
234
JsonValue enabledValue = config.get("enabled");
235
if (enabledValue.isBoolean()) {
236
boolean enabled = enabledValue.asBoolean();
237
System.out.println("Enabled: " + enabled);
238
}
239
}
240
241
// Safe array conversion
242
JsonValue serversValue = root.get("servers");
243
if (serversValue.isArray()) {
244
JsonArray servers = serversValue.asArray();
245
System.out.println("Server count: " + servers.size());
246
247
for (JsonValue serverValue : servers) {
248
if (serverValue.isString()) {
249
System.out.println("Server: " + serverValue.asString());
250
}
251
}
252
}
253
254
// Numeric precision handling
255
JsonValue versionValue = root.get("version");
256
if (versionValue.isNumber()) {
257
double version = versionValue.asDouble();
258
float versionFloat = versionValue.asFloat();
259
System.out.println("Version (double): " + version);
260
System.out.println("Version (float): " + versionFloat);
261
}
262
```
263
264
### Error-Safe Type Conversion
265
266
Handle type conversion errors gracefully with try-catch patterns.
267
268
```java
269
// Error-safe conversion patterns
270
public class SafeConverter {
271
public static String safeAsString(JsonValue value, String defaultValue) {
272
try {
273
return value.isString() ? value.asString() : defaultValue;
274
} catch (UnsupportedOperationException e) {
275
return defaultValue;
276
}
277
}
278
279
public static int safeAsInt(JsonValue value, int defaultValue) {
280
try {
281
return value.isNumber() ? value.asInt() : defaultValue;
282
} catch (UnsupportedOperationException | NumberFormatException e) {
283
return defaultValue;
284
}
285
}
286
287
public static boolean safeAsBoolean(JsonValue value, boolean defaultValue) {
288
try {
289
return value.isBoolean() ? value.asBoolean() : defaultValue;
290
} catch (UnsupportedOperationException e) {
291
return defaultValue;
292
}
293
}
294
295
public static JsonObject safeAsObject(JsonValue value) {
296
try {
297
return value.isObject() ? value.asObject() : null;
298
} catch (UnsupportedOperationException e) {
299
return null;
300
}
301
}
302
}
303
304
// Usage
305
JsonValue unknownValue = getValue(); // Could be any type
306
String stringValue = SafeConverter.safeAsString(unknownValue, "default");
307
int intValue = SafeConverter.safeAsInt(unknownValue, 0);
308
JsonObject objValue = SafeConverter.safeAsObject(unknownValue);
309
if (objValue != null) {
310
// Process object safely
311
}
312
```
313
314
### JsonType Enumeration
315
316
Enumeration of all possible JSON value types.
317
318
```java { .api }
319
/**
320
* Enumeration of JSON value types
321
*/
322
enum JsonType {
323
/**
324
* JSON string value
325
*/
326
STRING,
327
328
/**
329
* JSON number value
330
*/
331
NUMBER,
332
333
/**
334
* JSON object value
335
*/
336
OBJECT,
337
338
/**
339
* JSON array value
340
*/
341
ARRAY,
342
343
/**
344
* JSON boolean value (true or false)
345
*/
346
BOOLEAN,
347
348
/**
349
* JSON null value
350
*/
351
NULL,
352
353
/**
354
* Domain Specific Format value (specialized parsing)
355
*/
356
DSF
357
}
358
```
359
360
**Usage Examples:**
361
362
```java
363
import org.hjson.JsonType;
364
import org.hjson.JsonValue;
365
366
JsonValue mixed = JsonValue.readHjson("""
367
[
368
"text",
369
42,
370
true,
371
null,
372
{ key: "value" },
373
[1, 2, 3]
374
]
375
""").asArray();
376
377
// Process elements based on type
378
for (JsonValue element : mixed.asArray()) {
379
JsonType type = element.getType();
380
381
switch (type) {
382
case STRING:
383
System.out.println("String: " + element.asString());
384
break;
385
case NUMBER:
386
System.out.println("Number: " + element.asDouble());
387
break;
388
case BOOLEAN:
389
System.out.println("Boolean: " + element.asBoolean());
390
break;
391
case NULL:
392
System.out.println("Null value");
393
break;
394
case OBJECT:
395
System.out.println("Object with " + element.asObject().size() + " members");
396
break;
397
case ARRAY:
398
System.out.println("Array with " + element.asArray().size() + " elements");
399
break;
400
case DSF:
401
System.out.println("DSF value: " + element.asDsf());
402
break;
403
}
404
}
405
406
// Type-based filtering
407
public static List<JsonValue> filterByType(JsonArray array, JsonType targetType) {
408
List<JsonValue> filtered = new ArrayList<>();
409
for (JsonValue element : array) {
410
if (element.getType() == targetType) {
411
filtered.add(element);
412
}
413
}
414
return filtered;
415
}
416
417
// Usage
418
List<JsonValue> strings = filterByType(mixed.asArray(), JsonType.STRING);
419
List<JsonValue> numbers = filterByType(mixed.asArray(), JsonType.NUMBER);
420
```
421
422
## Error Handling
423
424
### ParseException
425
426
Exception thrown when parsing invalid JSON or Hjson input.
427
428
```java { .api }
429
/**
430
* Unchecked exception that indicates a problem while parsing JSON or Hjson text
431
* Provides detailed information about the location and nature of the parsing error
432
*/
433
class ParseException extends RuntimeException {
434
/**
435
* Returns the character offset at which the error occurred
436
* @return the character offset (0-based) where the error was detected
437
*/
438
int getOffset();
439
440
/**
441
* Returns the line number at which the error occurred
442
* @return the line number (1-based) where the error was detected
443
*/
444
int getLine();
445
446
/**
447
* Returns the column number at which the error occurred
448
* @return the column number (1-based) where the error was detected
449
*/
450
int getColumn();
451
}
452
```
453
454
**Usage Examples:**
455
456
```java
457
import org.hjson.JsonValue;
458
import org.hjson.ParseException;
459
460
// Parse error handling with detailed information
461
public class ParsingErrorHandler {
462
public static JsonValue safeParse(String text) {
463
try {
464
return JsonValue.readHjson(text);
465
} catch (ParseException e) {
466
System.err.printf("Parse error at line %d, column %d (offset %d): %s%n",
467
e.getLine(), e.getColumn(), e.getOffset(), e.getMessage());
468
469
// Show context around error location
470
showErrorContext(text, e.getOffset());
471
472
return null;
473
}
474
}
475
476
private static void showErrorContext(String text, int offset) {
477
int contextStart = Math.max(0, offset - 20);
478
int contextEnd = Math.min(text.length(), offset + 20);
479
480
String context = text.substring(contextStart, contextEnd);
481
String pointer = " ".repeat(offset - contextStart) + "^";
482
483
System.err.println("Context:");
484
System.err.println(context);
485
System.err.println(pointer);
486
}
487
}
488
489
// Common parsing errors and their handling
490
public void demonstrateParsingErrors() {
491
String[] invalidInputs = {
492
"{ name: unclosed", // Missing closing brace
493
"{ \"key\": invalid_value }", // Invalid unquoted value
494
"{ key: \"unclosed string }", // Unclosed string
495
"[1, 2, 3,]", // Trailing comma (in JSON mode)
496
"{ duplicate: 1, duplicate: 2 }" // Duplicate keys (warning)
497
};
498
499
for (String input : invalidInputs) {
500
System.out.println("Parsing: " + input);
501
try {
502
JsonValue result = JsonValue.readJSON(input); // Strict JSON parsing
503
System.out.println("Success: " + result);
504
} catch (ParseException e) {
505
System.err.printf("Error at %d:%d - %s%n",
506
e.getLine(), e.getColumn(), e.getMessage());
507
}
508
System.out.println();
509
}
510
}
511
```
512
513
### Error Recovery Strategies
514
515
```java
516
// Robust parsing with fallback strategies
517
public class RobustParser {
518
public static JsonValue parseWithFallback(String text) {
519
// Try Hjson first (more lenient)
520
try {
521
return JsonValue.readHjson(text);
522
} catch (ParseException hjsonError) {
523
System.out.println("Hjson parse failed, trying JSON...");
524
525
// Fallback to strict JSON
526
try {
527
return JsonValue.readJSON(text);
528
} catch (ParseException jsonError) {
529
// Both failed, try to fix common issues
530
String fixedText = attemptAutoFix(text);
531
if (!fixedText.equals(text)) {
532
try {
533
return JsonValue.readJSON(fixedText);
534
} catch (ParseException finalError) {
535
throw new RuntimeException("Unable to parse after auto-fix attempts", finalError);
536
}
537
}
538
539
// Give up with detailed error
540
throw new RuntimeException("Parse failed in both Hjson and JSON modes", jsonError);
541
}
542
}
543
}
544
545
private static String attemptAutoFix(String text) {
546
// Common fixes
547
String fixed = text
548
.replaceAll(",\\s*([}\\]])", "$1") // Remove trailing commas
549
.replaceAll("'", "\"") // Replace single quotes
550
.trim();
551
552
// Ensure proper bracing for objects
553
if (!fixed.startsWith("{") && !fixed.startsWith("[")) {
554
fixed = "{ " + fixed + " }";
555
}
556
557
return fixed;
558
}
559
}
560
```
561
562
### Type Conversion Error Handling
563
564
```java
565
// Handle type conversion errors
566
public class TypeSafeAccessor {
567
public static void processValue(JsonValue value) {
568
try {
569
if (value.isNumber()) {
570
// Handle potential overflow
571
try {
572
int intValue = value.asInt();
573
System.out.println("Integer: " + intValue);
574
} catch (NumberFormatException e) {
575
// Value too large for int, try long
576
try {
577
long longValue = value.asLong();
578
System.out.println("Long: " + longValue);
579
} catch (NumberFormatException e2) {
580
// Use double as fallback
581
double doubleValue = value.asDouble();
582
System.out.println("Double: " + doubleValue);
583
}
584
}
585
} else if (value.isString()) {
586
String stringValue = value.asString();
587
System.out.println("String: " + stringValue);
588
} else {
589
System.out.println("Other type: " + value.getType());
590
}
591
} catch (UnsupportedOperationException e) {
592
System.err.println("Unexpected type conversion error: " + e.getMessage());
593
}
594
}
595
}
596
```
597
598
## Validation Patterns
599
600
### Schema Validation
601
602
```java
603
// Simple schema validation using type checking
604
public class JsonValidator {
605
public static class ValidationResult {
606
private final boolean valid;
607
private final List<String> errors;
608
609
public ValidationResult(boolean valid, List<String> errors) {
610
this.valid = valid;
611
this.errors = errors;
612
}
613
614
public boolean isValid() { return valid; }
615
public List<String> getErrors() { return errors; }
616
}
617
618
public static ValidationResult validateUserObject(JsonValue value) {
619
List<String> errors = new ArrayList<>();
620
621
if (!value.isObject()) {
622
errors.add("Root value must be an object");
623
return new ValidationResult(false, errors);
624
}
625
626
JsonObject obj = value.asObject();
627
628
// Required string field
629
JsonValue nameValue = obj.get("name");
630
if (nameValue == null) {
631
errors.add("Missing required field: name");
632
} else if (!nameValue.isString()) {
633
errors.add("Field 'name' must be a string");
634
} else if (nameValue.asString().trim().isEmpty()) {
635
errors.add("Field 'name' cannot be empty");
636
}
637
638
// Required number field
639
JsonValue ageValue = obj.get("age");
640
if (ageValue == null) {
641
errors.add("Missing required field: age");
642
} else if (!ageValue.isNumber()) {
643
errors.add("Field 'age' must be a number");
644
} else {
645
try {
646
int age = ageValue.asInt();
647
if (age < 0 || age > 150) {
648
errors.add("Field 'age' must be between 0 and 150");
649
}
650
} catch (NumberFormatException e) {
651
errors.add("Field 'age' must be a valid integer");
652
}
653
}
654
655
// Optional boolean field
656
JsonValue activeValue = obj.get("active");
657
if (activeValue != null && !activeValue.isBoolean()) {
658
errors.add("Field 'active' must be a boolean if present");
659
}
660
661
return new ValidationResult(errors.isEmpty(), errors);
662
}
663
}
664
665
// Usage
666
JsonValue userData = JsonValue.readHjson("""
667
{
668
name: "John Doe"
669
age: 30
670
active: true
671
}
672
""");
673
674
ValidationResult result = JsonValidator.validateUserObject(userData);
675
if (result.isValid()) {
676
System.out.println("Validation passed");
677
} else {
678
System.out.println("Validation errors:");
679
result.getErrors().forEach(System.out::println);
680
}
681
```
682
683
## Best Practices
684
685
### Type Safety Guidelines
686
687
1. **Always check types before conversion**: Use `is*()` methods before `as*()` methods
688
2. **Handle conversion exceptions**: Wrap type conversions in try-catch blocks when input is untrusted
689
3. **Use appropriate numeric types**: Choose int/long/double based on expected value ranges
690
4. **Provide default values**: Use safe accessor patterns for optional values
691
5. **Validate input early**: Perform type validation as close to input as possible
692
693
### Error Handling Best Practices
694
695
1. **Provide context**: Include line/column information in error messages
696
2. **Log parsing errors**: Always log ParseException details for debugging
697
3. **Graceful degradation**: Provide fallback parsing strategies when possible
698
4. **User-friendly messages**: Convert technical errors to user-friendly messages
699
5. **Early validation**: Validate structure and types immediately after parsing
700
701
### Performance Considerations
702
703
1. **Type checking cost**: Type checking is fast, conversion may involve validation
704
2. **Exception handling**: Avoid using exceptions for control flow
705
3. **Caching**: Cache type information for frequently accessed values
706
4. **Batch validation**: Validate entire structures in one pass when possible