Architectural Overview
simple_json is designed as a high-level facade over the standard Eiffel JSON library (eJSON), adding modern features like JSON Schema validation, JSON Pointer, JSON Patch, and JSONPath queries while maintaining full compatibility with the underlying parser.
Design Philosophy
- Facade Pattern: Simple API hiding complex internals
- Builder Pattern: Fluent interface for constructing JSON
- Wrapper Pattern: Type-safe wrappers around JSON values
- Standards Compliance: RFC 6901, RFC 6902, RFC 7386, JSON Schema Draft 7
Class Hierarchy
SIMPLE_JSON_CONSTANTS (shared constants)
|
+-- SIMPLE_JSON (facade)
|
+-- SIMPLE_JSON_VALUE (value wrapper)
|
+-- SIMPLE_JSON_OBJECT (object builder)
|
+-- SIMPLE_JSON_ARRAY (array builder)
|
+-- SIMPLE_JSON_POINTER (RFC 6901)
|
+-- SIMPLE_JSON_SCHEMA_VALIDATOR
|
+-- SIMPLE_JSON_PATCH
| |
| +-- SIMPLE_JSON_PATCH_OPERATION (abstract)
| |
| +-- SIMPLE_JSON_PATCH_ADD
| +-- SIMPLE_JSON_PATCH_REMOVE
| +-- SIMPLE_JSON_PATCH_REPLACE
| +-- SIMPLE_JSON_PATCH_MOVE
| +-- SIMPLE_JSON_PATCH_COPY
| +-- SIMPLE_JSON_PATCH_TEST
|
+-- SIMPLE_JSON_MERGE_PATCH (RFC 7386)
|
+-- SIMPLE_JSON_STREAM (streaming parser)
|
+-- SIMPLE_JSON_STREAM_CURSOR
+-- SIMPLE_JSON_STREAM_ELEMENT
Module Organization
| Module | Contents | Purpose |
|---|---|---|
src/core/ |
SIMPLE_JSON, SIMPLE_JSON_VALUE, SIMPLE_JSON_OBJECT, SIMPLE_JSON_ARRAY | Core parsing and building |
src/pointer/ |
SIMPLE_JSON_POINTER | RFC 6901 navigation |
src/schema/ |
SIMPLE_JSON_SCHEMA, SIMPLE_JSON_SCHEMA_VALIDATOR | JSON Schema validation |
src/patch/ |
SIMPLE_JSON_PATCH, patch operations | RFC 6902 modifications |
src/merge_patch/ |
SIMPLE_JSON_MERGE_PATCH | RFC 7386 merge |
src/streaming/ |
SIMPLE_JSON_STREAM | Large file processing |
src/utilities/ |
SIMPLE_JSON_PRETTY_PRINTER | Formatting utilities |
src/constants/ |
SIMPLE_JSON_CONSTANTS | Shared constants |
Relationship to eJSON
simple_json wraps the standard Eiffel JSON library (eJSON) rather than replacing it:
+-------------------+
| SIMPLE_JSON | (Facade)
+-------------------+
|
+---------------+---------------+
| |
+---------------+ +---------------+
|SIMPLE_JSON_ | |SIMPLE_JSON_ |
| VALUE | | OBJECT |
+---------------+ +---------------+
| |
| wraps | wraps
v v
+---------------+ +---------------+
| JSON_VALUE | | JSON_OBJECT | (eJSON)
+---------------+ +---------------+
Why Wrap Instead of Replace?
- Proven Parser: eJSON's parser is battle-tested
- Unicode Support: Proper UTF-8/UTF-32 handling
- Standards Compliance: JSON spec compliance
- Interoperability: Can use raw eJSON when needed
What simple_json Adds
- High-level facade with builder pattern
- STRING_32 throughout (instead of STRING_8)
- Detailed error tracking with line/column
- JSON Schema validation (unique in Eiffel)
- JSON Pointer, Patch, Merge Patch
- JSONPath queries
- Streaming parser
Facade Pattern
The SIMPLE_JSON class serves as the primary facade, providing a simplified interface for common operations:
class
SIMPLE_JSON
feature -- Parsing
parse (a_json_text: STRING_32): detachable SIMPLE_JSON_VALUE
parse_file (a_file_path: STRING_32): detachable SIMPLE_JSON_VALUE
is_valid_json (a_json_text: STRING_32): BOOLEAN
feature -- Building
new_object: SIMPLE_JSON_OBJECT
new_array: SIMPLE_JSON_ARRAY
string_value (a_string: STRING_32): SIMPLE_JSON_VALUE
-- ... more value creators
feature -- Queries
query_string (a_value: SIMPLE_JSON_VALUE; a_path: STRING_32): detachable STRING_32
-- ... more query methods
feature -- Patching
create_patch: SIMPLE_JSON_PATCH
apply_patch (...): SIMPLE_JSON_PATCH_RESULT
feature -- Errors
has_errors: BOOLEAN
last_errors: ARRAYED_LIST [SIMPLE_JSON_ERROR]
-- ... more error methods
end
Benefits of Facade
- Single entry point - one class to learn
- Discoverable API - related features grouped together
- Hides complexity - no need to know about eJSON internals
- Consistent error handling across all operations
Builder Pattern
JSON construction uses a fluent builder pattern where methods return like Current for chaining:
class
SIMPLE_JSON_OBJECT
feature -- Builder
put_string (a_key, a_value: STRING_32): like Current
do
-- Add string to underlying JSON_OBJECT
Result := Current
ensure
chained: Result = Current
has_key: has (a_key)
end
put_integer (a_key: STRING_32; a_value: INTEGER_64): like Current
do
-- Add integer to underlying JSON_OBJECT
Result := Current
ensure
chained: Result = Current
end
end
Usage
-- Fluent construction
obj := json.new_object
.put_string ("name", "Alice")
.put_integer ("age", 30)
.put_boolean ("active", True)
.put_object ("address", json.new_object
.put_string ("city", "Boston")
.put_string ("state", "MA"))
Wrapper Pattern
SIMPLE_JSON_VALUE wraps JSON_VALUE to provide type-safe access with Unicode support:
class
SIMPLE_JSON_VALUE
feature {NONE}
json_value: JSON_VALUE -- Wrapped eJSON value
feature -- Type checking
is_string: BOOLEAN
do Result := json_value.is_string end
is_object: BOOLEAN
do Result := json_value.is_object end
feature -- Access
as_string_32: STRING_32
require
is_string: is_string
do
-- Convert from eJSON STRING_8 to STRING_32
end
as_object: SIMPLE_JSON_OBJECT
require
is_object: is_object
do
-- Wrap JSON_OBJECT in SIMPLE_JSON_OBJECT
end
end
Key Design Decisions
- STRING_32: All string access returns STRING_32 for proper Unicode
- Preconditions: Type-checking methods have corresponding preconditions
- Lazy Wrapping: Nested objects/arrays wrapped on demand
Error Handling Architecture
The library uses a structured error model with position tracking:
class
SIMPLE_JSON_ERROR
feature -- Access
message: STRING_32
position: INTEGER -- Character offset in source
line: INTEGER -- Calculated line number
column: INTEGER -- Calculated column number
feature -- Output
to_detailed_string: STRING_32
-- Shows error with source context:
-- Line 5, Column 12: Unexpected token
-- {"name": "Alice", age: 30}
-- ^
end
Error Flow
parse() called
|
v
JSON_PARSER.parse_content
|
+-- Success --> Create SIMPLE_JSON_VALUE
|
+-- Failure --> capture_parser_errors()
|
v
Extract position from error string
Calculate line/column from position
Create SIMPLE_JSON_ERROR with context
Add to last_errors list
JSONPath Implementation
JSONPath queries are implemented as path parsing + recursive navigation:
-- Path: $.users[0].name
-- Segments: ["users", "[0]", "name"]
query_single_value (a_value: SIMPLE_JSON_VALUE; a_path: STRING_32)
local
l_segments: LIST [STRING_32]
l_current: detachable SIMPLE_JSON_VALUE
do
l_segments := parse_json_path (a_path) -- Split into segments
l_current := a_value
across l_segments as ic until l_current = Void loop
l_current := navigate_segment (l_current, ic) -- Navigate one level
end
Result := l_current
end
Wildcard Expansion
-- Path: $.users[*].name
-- Expands [*] to iterate all array elements
query_multiple_values (a_value: SIMPLE_JSON_VALUE; a_path: STRING_32)
do
-- Maintains SET of current values
-- At wildcard segments, expands set
-- At regular segments, navigates each
end
JSON Pointer (RFC 6901)
Simpler than JSONPath - just forward-slash separated segments:
-- Pointer: /users/0/name
-- Segments: ["users", "0", "name"]
parse_path (a_path: STRING_32): BOOLEAN
do
-- Split by "/"
-- Unescape ~0 (tilde) and ~1 (slash)
-- Store in segments list
end
value_at (a_root: SIMPLE_JSON_VALUE): detachable SIMPLE_JSON_VALUE
do
-- Navigate each segment
-- For objects: lookup by key
-- For arrays: parse segment as integer index
end
JSON Patch (RFC 6902)
Patch operations are implemented as a command pattern:
deferred class
SIMPLE_JSON_PATCH_OPERATION
feature
path: STRING_32
is_valid: BOOLEAN
apply (a_document: SIMPLE_JSON_VALUE): SIMPLE_JSON_PATCH_RESULT
deferred
end
end
class SIMPLE_JSON_PATCH_ADD
inherit SIMPLE_JSON_PATCH_OPERATION
feature
value: SIMPLE_JSON_VALUE
apply (a_document: SIMPLE_JSON_VALUE): SIMPLE_JSON_PATCH_RESULT
do
-- Navigate to parent of path
-- Add value at final segment
end
end
Patch Application
SIMPLE_JSON_PATCH.apply (document)
|
v
For each operation in operations:
|
+-- Parse path into JSON Pointer
|
+-- Navigate to target location
|
+-- Execute operation (add/remove/replace/move/copy/test)
|
+-- On failure: return error result immediately
|
+-- On success: continue to next operation
|
v
Return success with modified document
Schema Validation Architecture
JSON Schema validation follows Draft 7 specification:
SIMPLE_JSON_SCHEMA_VALIDATOR.validate (instance, schema)
|
+-- validate_type (instance, schema)
| |
| +-- Check "type" keyword matches instance type
|
+-- Type-specific validation:
|
+-- String: minLength, maxLength, pattern
+-- Number: minimum, maximum
+-- Object: properties, required, additionalProperties
+-- Array: items, minItems, maxItems
Validation Result
class
SIMPLE_JSON_SCHEMA_VALIDATION_RESULT
feature
is_valid: BOOLEAN
errors: ARRAY [SIMPLE_JSON_SCHEMA_VALIDATION_ERROR]
-- Each error has:
-- path: JSON Pointer to invalid location
-- message: Human-readable description
-- keyword: Schema keyword that failed
end
Streaming Parser Architecture
For large files, the streaming parser processes JSON token-by-token:
SIMPLE_JSON_STREAM
|
+-- Character buffer (file or string)
|
+-- Tokenizer (produces tokens)
|
+-- Cursor (iterates tokens)
|
+-- is_object_start, is_object_end
+-- is_array_start, is_array_end
+-- is_key, is_string, is_number
+-- forth (advance to next token)
Memory Efficiency
Unlike DOM parsing which loads entire document into memory, streaming:
- Reads file in chunks
- Produces tokens on demand
- Discards processed tokens
- Constant memory regardless of file size
Unicode Handling
All string operations use STRING_32 for proper Unicode support:
-- Internal conversion
utf_converter: UTF_CONVERTER
once
create Result
end
-- String input: STRING_32 -> UTF-8 -> eJSON parser
l_utf8 := utf_converter.utf_32_string_to_utf_8_string_8 (a_json_text)
-- String output: eJSON -> UTF-8 -> STRING_32
Result := utf_converter.utf_8_string_8_to_string_32 (l_json_string)
Design by Contract
The library uses comprehensive contracts throughout:
feature -- Parsing
parse (a_json_text: STRING_32): detachable SIMPLE_JSON_VALUE
require
not_empty: not a_json_text.is_empty
ensure
errors_cleared_on_success: Result /= Void implies not has_errors
invariant
-- Error list integrity
last_errors_attached: last_errors /= Void
-- Error state consistency
has_errors_definition: has_errors = not last_errors.is_empty
-- No void errors in list
no_void_errors: across last_errors as ic all ic /= Void end
SCOOP Compatibility
The library is designed for SCOOP (Simple Concurrent Object-Oriented Programming):
- No global state: Each SIMPLE_JSON instance is independent
- Immutable values: SIMPLE_JSON_VALUE wraps don't mutate underlying JSON
- Thread-safe once features: UTF converter is once-per-thread
- ECF configured:
concurrency="scoop"
-- Safe in SCOOP context
separate
json_processor: SIMPLE_JSON
do
-- Each processor has its own error state
-- No shared mutable state
end