Getting Started
Installation
- Set the environment variable:
export SIMPLE_EIFFEL=/d/prod - Add to your ECF file:
<library name="simple_json" location="$SIMPLE_EIFFEL/simple_json/simple_json.ecf"/>
Your First JSON Parser
class
MY_APP
feature
json: SIMPLE_JSON
once
create Result
end
demo
local
value: SIMPLE_JSON_VALUE
do
-- Parse JSON text
if attached json.parse ('{"name": "Alice", "age": 30}') as v then
print (v.as_object.string_item ("name")) -- "Alice"
print (v.as_object.integer_item ("age")) -- 30
end
end
end
Parsing JSON
Parse from String
local
json: SIMPLE_JSON
value: SIMPLE_JSON_VALUE
do
create json
-- Parse object
if attached json.parse ('{"key": "value"}') as v then
-- Use v
end
-- Parse array
if attached json.parse ('[1, 2, 3]') as arr then
-- Use arr
end
end
Parse from File
if attached json.parse_file ("config.json") as config then
-- Use config
else
print (json.errors_as_string)
end
Validate Without Parsing
if json.is_valid_json (user_input) then
-- Safe to parse
else
print ("Invalid JSON: " + json.errors_as_string)
end
Error Handling
if attached json.parse (bad_json) as v then
-- Success
else
-- Detailed error information
if json.has_errors then
print ("Error count: " + json.error_count.out)
-- First error only
if attached json.first_error as err then
print (err.message)
print ("Line: " + err.line.out)
print ("Column: " + err.column.out)
end
-- All errors with context
print (json.detailed_errors)
end
end
Building JSON
Build Objects
local
json: SIMPLE_JSON
obj: SIMPLE_JSON_OBJECT
do
create json
-- Fluent builder pattern
obj := json.new_object
.put_string ("name", "Alice")
.put_integer ("age", 30)
.put_boolean ("active", True)
.put_null ("nickname")
print (obj.to_json)
-- {"name":"Alice","age":30,"active":true,"nickname":null}
end
Build Arrays
local
arr: SIMPLE_JSON_ARRAY
do
arr := json.new_array
.add_string ("apple")
.add_string ("banana")
.add_integer (42)
.add_boolean (True)
print (arr.to_json) -- ["apple","banana",42,true]
end
Nested Structures
local
person: SIMPLE_JSON_OBJECT
address: SIMPLE_JSON_OBJECT
hobbies: SIMPLE_JSON_ARRAY
do
-- Build address sub-object
address := json.new_object
.put_string ("street", "123 Main St")
.put_string ("city", "Boston")
-- Build hobbies array
hobbies := json.new_array
.add_string ("reading")
.add_string ("coding")
-- Build person with nested data
person := json.new_object
.put_string ("name", "Alice")
.put_object ("address", address)
.put_array ("hobbies", hobbies)
print (person.to_json)
end
JSONPath Queries
JSONPath lets you query JSON using path expressions, similar to XPath for XML.
Basic Path Queries
local
json: SIMPLE_JSON
v: SIMPLE_JSON_VALUE
do
if attached json.parse ('{"person": {"name": "Alice", "age": 30}}') as value then
-- Query nested string
if attached json.query_string (value, "$.person.name") as name then
print (name) -- "Alice"
end
-- Query nested integer
print (json.query_integer (value, "$.person.age")) -- 30
end
end
Array Access
-- JSON: {"users": [{"name": "Alice"}, {"name": "Bob"}]}
-- Access specific array element (0-indexed)
json.query_string (v, "$.users[0].name") -- "Alice"
json.query_string (v, "$.users[1].name") -- "Bob"
Wildcard Queries
-- JSON: {"hobbies": ["reading", "coding", "gaming"]}
-- Get all array elements
local
hobbies: ARRAYED_LIST [STRING_32]
do
hobbies := json.query_strings (v, "$.hobbies[*]")
-- ["reading", "coding", "gaming"]
end
-- JSON: {"people": [{"name": "Alice"}, {"name": "Bob"}]}
-- Get all names from array of objects
names := json.query_strings (v, "$.people[*].name")
-- ["Alice", "Bob"]
JSON Pointer (RFC 6901)
JSON Pointer is a simpler alternative to JSONPath, using forward slashes.
local
pointer: SIMPLE_JSON_POINTER
v: SIMPLE_JSON_VALUE
do
-- Parse JSON
if attached json.parse ('{"users": [{"name": "Alice"}]}') as root then
create pointer
-- Navigate with pointer
if pointer.parse_path ("/users/0/name") then
if attached pointer.value_at (root) as val then
print (val.as_string_32) -- "Alice"
end
end
end
end
JSONPath vs JSON Pointer
| JSONPath | JSON Pointer | Description |
|---|---|---|
$.name |
/name |
Root property |
$.users[0] |
/users/0 |
Array element |
$.users[*].name |
N/A | Wildcards (JSONPath only) |
JSON Schema Validation
simple_json supports JSON Schema Draft 7 validation - the only Eiffel library with this capability.
Basic Schema Validation
local
schema: SIMPLE_JSON_SCHEMA
validator: SIMPLE_JSON_SCHEMA_VALIDATOR
result: SIMPLE_JSON_SCHEMA_VALIDATION_RESULT
do
-- Define schema
create schema.make_from_string ('{"type": "object", "properties": {"name": {"type": "string"}, "age": {"type": "integer", "minimum": 0}}}')
-- Create validator
create validator.make
-- Validate data
if attached json.parse ('{"name": "Alice", "age": 30}') as data then
result := validator.validate (data, schema)
if result.is_valid then
print ("Valid!")
else
across result.errors as err loop
print (err.message)
end
end
end
end
Supported Schema Keywords
- type: string, number, integer, object, array, boolean, null
- properties: Object property schemas
- required: Required property names
- minimum, maximum: Number constraints
- minLength, maxLength: String length constraints
- pattern: Regex pattern for strings
- minItems, maxItems: Array length constraints
- items: Array item schema
JSON Patch (RFC 6902)
JSON Patch lets you describe modifications to a JSON document.
Create and Apply Patches
local
patch: SIMPLE_JSON_PATCH
result: SIMPLE_JSON_PATCH_RESULT
do
-- Create patch
patch := json.create_patch
patch.add_add ("/email", json.string_value ("alice@example.com"))
patch.add_replace ("/age", json.integer_value (31))
patch.add_remove ("/temporary")
-- Apply to document
if attached json.parse (original_json) as doc then
result := patch.apply (doc)
if result.is_success then
print (result.result_document.to_json)
else
print ("Patch failed: " + result.error_message)
end
end
end
Patch Operations
| Operation | Method | Description |
|---|---|---|
| add | add_add (path, value) |
Add value at path |
| remove | add_remove (path) |
Remove value at path |
| replace | add_replace (path, value) |
Replace value at path |
| move | add_move (from, path) |
Move value from one path to another |
| copy | add_copy (from, path) |
Copy value from one path to another |
| test | add_test (path, value) |
Test that path equals value |
Parse Patch from JSON
local
patch_json: STRING
result: SIMPLE_JSON_PATCH_RESULT
do
patch_json := '[{"op": "add", "path": "/email", "value": "alice@example.com"}, {"op": "replace", "path": "/age", "value": 31}]'
result := json.apply_patch (document, patch_json)
end
JSON Merge Patch (RFC 7386)
A simpler alternative to JSON Patch - just provide the fields you want to change.
local
merge: SIMPLE_JSON_MERGE_PATCH
result: SIMPLE_JSON_MERGE_PATCH_RESULT
do
-- Original: {"name": "Alice", "age": 30, "temp": true}
-- Patch: {"age": 31, "email": "alice@ex.com", "temp": null}
-- Result: {"name": "Alice", "age": 31, "email": "alice@ex.com"}
create merge
result := merge.apply (original, patch)
if result.is_success then
print (result.result_document.to_json)
end
end
Merge Patch Rules
- Properties in patch replace existing properties
- New properties are added
nullvalues remove properties- Properties not in patch are unchanged
Streaming Parser
For large files, use the streaming parser to process JSON incrementally.
local
stream: SIMPLE_JSON_STREAM
cursor: SIMPLE_JSON_STREAM_CURSOR
do
create stream.make_from_file ("large_file.json")
from
cursor := stream.new_cursor
until
cursor.after
loop
if cursor.is_object_start then
print ("Object started")
elseif cursor.is_key then
print ("Key: " + cursor.key_name)
elseif cursor.is_string then
print ("Value: " + cursor.string_value)
end
cursor.forth
end
end
Entity Serialization
Implement SIMPLE_JSON_SERIALIZABLE for automatic JSON conversion.
class
PERSON
inherit
SIMPLE_JSON_SERIALIZABLE
feature -- Access
name: STRING_32
age: INTEGER
email: detachable STRING_32
feature -- Serialization
to_json_object: SIMPLE_JSON_OBJECT
do
Result := json.new_object
.put_string ("name", name)
.put_integer ("age", age)
if attached email as e then
Result.put_string ("email", e)
end
end
from_json (a_value: SIMPLE_JSON_VALUE)
do
if a_value.is_object then
if attached a_value.as_object.string_item ("name") as n then
name := n
end
age := a_value.as_object.integer_item ("age")
email := a_value.as_object.string_item ("email")
end
end
end
Usage
local
person: PERSON
do
-- Serialize to JSON
create person
person.set_name ("Alice")
person.set_age (30)
print (person.to_json)
-- Deserialize from JSON
if attached json.parse (json_text) as v then
create person
person.from_json (v)
end
end
Unicode Support
simple_json uses STRING_32 throughout for proper Unicode support.
local
obj: SIMPLE_JSON_OBJECT
do
-- Unicode strings work natively
obj := json.new_object
.put_string ("greeting", "Hello")
.put_string ("emoji", "!")
print (obj.to_json)
-- Properly encoded UTF-8 output
end