diff --git a/.github/workflows/php81.yaml b/.github/workflows/php81.yaml
index fead114..77e68ff 100644
--- a/.github/workflows/php81.yaml
+++ b/.github/workflows/php81.yaml
@@ -8,14 +8,14 @@ on:
jobs:
test:
name: Run Tests
- uses: WebFiori/workflows/.github/workflows/test-php.yaml@main
+ uses: WebFiori/workflows/.github/workflows/test-php.yaml@v1.2.1
with:
php-version: '8.1'
code-coverage:
name: Coverage
needs: test
- uses: WebFiori/workflows/.github/workflows/coverage-codecov.yaml@main
+ uses: WebFiori/workflows/.github/workflows/coverage-codecov.yaml@v1.2.1
with:
php-version: '8.1'
coverage-file: 'php-8.1-coverage.xml'
diff --git a/.github/workflows/php82.yaml b/.github/workflows/php82.yaml
index 978033a..9e431d3 100644
--- a/.github/workflows/php82.yaml
+++ b/.github/workflows/php82.yaml
@@ -8,7 +8,7 @@ on:
jobs:
test:
name: Run Tests
- uses: WebFiori/workflows/.github/workflows/test-php.yaml@main
+ uses: WebFiori/workflows/.github/workflows/test-php.yaml@v1.2.1
with:
php-version: '8.2'
phpunit-config: "tests/phpunit10.xml"
@@ -16,7 +16,7 @@ jobs:
code-coverage:
name: Coverage
needs: test
- uses: WebFiori/workflows/.github/workflows/coverage-codecov.yaml@main
+ uses: WebFiori/workflows/.github/workflows/coverage-codecov.yaml@v1.2.1
with:
php-version: '8.2'
coverage-file: 'php-8.2-coverage.xml'
diff --git a/.github/workflows/php83.yaml b/.github/workflows/php83.yaml
index 8ecb808..616f348 100644
--- a/.github/workflows/php83.yaml
+++ b/.github/workflows/php83.yaml
@@ -12,7 +12,7 @@ jobs:
test:
name: Run Tests
- uses: WebFiori/workflows/.github/workflows/test-php.yaml@main
+ uses: WebFiori/workflows/.github/workflows/test-php.yaml@v1.2.1
with:
php-version: '8.3'
phpunit-config: 'tests/phpunit10.xml'
@@ -21,7 +21,7 @@ jobs:
code-coverage:
name: Coverage
needs: test
- uses: WebFiori/workflows/.github/workflows/coverage-codecov.yaml@main
+ uses: WebFiori/workflows/.github/workflows/coverage-codecov.yaml@v1.2.1
with:
php-version: '8.3'
coverage-file: 'php-8.3-coverage.xml'
@@ -31,15 +31,13 @@ jobs:
code-quality:
name: Code Quality
needs: test
- uses: WebFiori/workflows/.github/workflows/quality-sonarcloud.yaml@main
- with:
- coverage-file: 'php-8.3-coverage.xml'
+ uses: WebFiori/workflows/.github/workflows/quality-sonarcloud.yaml@v1.2.1
secrets:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
release-prod:
name: Prepare Production Release Branch / Publish Release
needs: [code-coverage, code-quality]
- uses: WebFiori/workflows/.github/workflows/release-php.yaml@main
+ uses: WebFiori/workflows/.github/workflows/release-php.yaml@v1.2.1
with:
branch: 'main'
diff --git a/.github/workflows/php84.yaml b/.github/workflows/php84.yaml
index 1ce3097..adcc94b 100644
--- a/.github/workflows/php84.yaml
+++ b/.github/workflows/php84.yaml
@@ -8,7 +8,7 @@ on:
jobs:
test:
name: Run Tests
- uses: WebFiori/workflows/.github/workflows/test-php.yaml@main
+ uses: WebFiori/workflows/.github/workflows/test-php.yaml@v1.2.1
with:
php-version: '8.4'
phpunit-config: "tests/phpunit10.xml"
@@ -16,7 +16,7 @@ jobs:
code-coverage:
name: Coverage
needs: test
- uses: WebFiori/workflows/.github/workflows/coverage-codecov.yaml@main
+ uses: WebFiori/workflows/.github/workflows/coverage-codecov.yaml@v1.2.1
with:
php-version: '8.4'
coverage-file: 'php-8.4-coverage.xml'
diff --git a/.github/workflows/php85.yaml b/.github/workflows/php85.yaml
new file mode 100644
index 0000000..e66edfc
--- /dev/null
+++ b/.github/workflows/php85.yaml
@@ -0,0 +1,30 @@
+name: PHP 8.5
+
+on:
+ push:
+ branches: [ main ]
+ pull_request:
+ branches: [ main, dev ]
+env:
+ OPERATING_SYS: ubuntu-latest
+ PHP_VERSION: 8.5
+jobs:
+
+ test:
+ name: Run Tests
+ uses: WebFiori/workflows/.github/workflows/test-php.yaml@v1.2.1
+ with:
+ php-version: '8.5'
+ phpunit-config: 'tests/phpunit10.xml'
+
+
+ code-coverage:
+ name: Coverage
+ needs: test
+ uses: WebFiori/workflows/.github/workflows/coverage-codecov.yaml@v1.2.1
+ with:
+ php-version: '8.5'
+ coverage-file: 'php-8.5-coverage.xml'
+ secrets:
+ CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
+
diff --git a/README.md b/README.md
index 9dda357..af879b2 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
# WebFiori Json
-A helper class library for creating JSON or JSONx strings in PHP. It can be used to create well-formatted JSON strings from any variable type (strings, numbers, booleans, arrays, and even objects).
+A PHP library for creating and parsing JSON and JSONx strings. Supports all PHP scalar types, arrays, and objects with flexible property naming styles.
@@ -18,41 +18,30 @@ A helper class library for creating JSON or JSONx strings in PHP. It can be used
## Table of Contents
-* [What is JSON?](#what-is-json)
* [Features](#features)
* [Supported PHP Versions](#supported-php-versions)
* [Installation](#installation)
* [Basic Usage](#basic-usage)
- * [Example](#example)
- * [Using the Constructor](#using-the-constructor)
-* [Converting Properties Case](#converting-properties-case)
- * [Available Styles](#available-styles)
- * [Letter Case Options](#letter-case-options)
-* [Reading From Files](#reading-from-files)
+* [Working With Arrays](#working-with-arrays)
* [Working With Objects](#working-with-objects)
* [Using JsonI Interface](#using-jsoni-interface)
* [Auto-Mapping Objects](#auto-mapping-objects)
-* [Decoding JSON String](#decoding-json-string)
-* [Storing Output](#storing-output)
-* [Working With Arrays](#working-with-arrays)
- * [Arrays as Objects](#arrays-as-objects)
+* [Property Naming Styles](#property-naming-styles)
+* [Decoding JSON](#decoding-json)
+* [Saving to File](#saving-to-file)
* [JSONx](#jsonx)
* [Error Handling](#error-handling)
* [API Reference](#api-reference)
-## What is JSON?
-
-According to [json.org](https://www.json.org/json-en.html), JSON is a data exchange format which is based partially on JavaScript. It is easy for humans to read and for machines to understand. JSON data is represented as pairs of keys and values.
-
## Features
-* Support for creating well-formatted JSON with proper indentation and escaping
-* Support for creating [JSONx](https://www.ibm.com/docs/en/datapower-gateways/10.0.1?topic=20-jsonx) (XML representation of JSON)
-* Ability to decode JSON strings and convert them to `Json` objects
-* Ability to read JSON files and map JSON values to PHP data types
-* Ability to manipulate JSON properties as needed
-* Support for different property naming styles (camelCase, kebab-case, snake_case)
-* Support for different letter cases (same, upper, lower)
-* Customizable object serialization through the `JsonI` interface
+* Create well-formatted JSON strings from any PHP value (scalars, arrays, objects)
+* Decode JSON strings and files into `Json` objects
+* Flexible property naming styles: `camelCase`, `kebab-case`, `snake_case`, or `none`
+* Letter case control: `same`, `upper`, `lower`
+* Custom object serialization via the `JsonI` interface
+* Auto-mapping of plain objects via public getter methods and public properties
+* [JSONx](https://www.ibm.com/docs/en/datapower-gateways/10.0.1?topic=20-jsonx) output (XML representation of JSON)
+* Save JSON output directly to a file
## Supported PHP Versions
| Build Status |
@@ -63,7 +52,6 @@ According to [json.org](https://www.json.org/json-en.html), JSON is a data excha
|
|
## Installation
-If you are using composer to manage your dependencies, then it is possible to install the library by including the entry `"webfiori/jsonx":"*"` in the `require` section of your `composer.json` file to install the latest release:
```json
{
@@ -73,208 +61,100 @@ If you are using composer to manage your dependencies, then it is possible to in
}
```
-Alternatively, you can install a specific version:
+Or for a specific version:
```json
{
"require": {
- "webfiori/jsonx": "^1.0"
+ "webfiori/jsonx": "^3.0"
}
}
```
-Another way to include the library is by going to [releases](https://github.com/WebFiori/json/releases) and downloading the latest release, then extracting the compressed file content and adding it to your include directory.
-
-## Basic Usage
-The process of using the classes is very simple. What you have to do is the following steps:
-
- * Import (or include) the class `Json` from the namespace `WebFiori\Json`
- * Create an instance of the class
- * Add data as needed using the various `add` methods
- * Output the object using `echo` command or any similar one
-
-### Example
-The following code shows a very simple usage example:
-
-```php
-//load the class "Json"
-require_once 'vendor/autoload.php'; // If using Composer
-// OR require_once 'path/to/WebFiori/Json/Json.php'; // If manually installed
-
-use WebFiori\Json\Json;
-
-//initialize an object of the class Json
-$j = new Json();
-
-//add a number attribute
-$j->addNumber('my-number', 34);
-
-//add a boolean with 'false' as its value.
-$j->addBoolean('my-boolean', false);
-
-//add a string
-$j->addString('a-string', 'Hello, I\'m Json! I like "JSON". ');
-
-header('content-type:application/json');
-
-// Output the JSON string
-echo $j;
-```
-
-The output of the code will be:
+Then run:
-```json
-{
- "my-number":34,
- "my-boolean":false,
- "a-string":"Hello, I'm Json! I like \"JSON\". "
-}
+```bash
+composer install
```
-### Using the Constructor
-
-You can also add data directly using the constructor by passing an associative array:
+## Basic Usage
```php
use WebFiori\Json\Json;
-$jsonObj = new Json([
- 'first-name' => 'Ibrahim',
- 'last-name' => 'BinAlshikh',
- 'age' => 26,
- 'is-married' => true,
- 'mobile-number' => null
+$json = new Json([
+ 'name' => 'Ibrahim',
+ 'age' => 30,
+ 'married' => false,
+ 'score' => 9.5,
+ 'notes' => null,
]);
-echo $jsonObj;
+echo $json;
```
-The JSON output of this code will be:
+Output:
```json
-{
- "first-name":"Ibrahim",
- "last-name":"BinAlshikh",
- "age":26,
- "is-married":true,
- "mobile-number":null
-}
+{"name":"Ibrahim","age":30,"married":false,"score":9.5,"notes":null}
```
-## Converting Properties Case
-
-The library supports different property naming styles and letter cases. You can set these when creating a Json object or change them later.
-
-### Available Styles
-
-The following property naming styles are supported:
-
-* `none`: Keep the property names as they are provided
-* `camel`: Convert property names to camelCase
-* `kebab`: Convert property names to kebab-case
-* `snake`: Convert property names to snake_case
-
-### Letter Case Options
-
-The following letter case options are available:
-
-* `same`: Keep the letter case as provided
-* `upper`: Convert all letters to uppercase
-* `lower`: Convert all letters to lowercase
-
-Example:
+You can also build the object incrementally using the `add*()` methods:
```php
-use WebFiori\Json\Json;
-
-// Set style and case in constructor
-$json = new Json([], 'camel', 'lower');
-
-// Add properties
-$json->add('first-name', 'Ibrahim');
-$json->add('last-name', 'BinAlshikh');
+$json = new Json();
+$json->addString('name', 'Ibrahim');
+$json->addNumber('age', 30);
+$json->addBoolean('married', false);
+$json->addNull('notes');
echo $json;
```
-Output:
+## Working With Arrays
-```json
-{
- "firstname":"Ibrahim",
- "lastname":"BinAlshikh"
-}
-```
+```php
+$json = new Json();
-You can also change the style after creating the object:
+// Indexed array
+$json->addArray('tags', ['php', 'json', 'api']);
+
+// Associative array represented as a JSON object
+$json->addArray('address', ['city' => 'Riyadh', 'country' => 'SA'], true);
-```php
-$json->setPropsStyle('snake', 'upper');
echo $json;
```
Output:
```json
-{
- "FIRST_NAME":"Ibrahim",
- "LAST_NAME":"BinAlshikh"
-}
-```
-
-## Reading From Files
-
-The library provides a static method to read JSON data from files:
-
-```php
-use WebFiori\Json\Json;
-
-try {
- $jsonObj = Json::fromJsonFile('/path/to/file.json');
-
- // Access properties
- $value = $jsonObj->get('propertyName');
-
- echo $value;
-} catch (\WebFiori\Json\JsonException $ex) {
- echo 'Error: ' . $ex->getMessage();
-}
+{"tags":["php","json","api"],"address":{"city":"Riyadh","country":"SA"}}
```
## Working With Objects
### Using JsonI Interface
-For custom object serialization, you can implement the `JsonI` interface:
+Implement `JsonI` to fully control how an object is serialized:
```php
use WebFiori\Json\Json;
use WebFiori\Json\JsonI;
-class Person implements JsonI {
- private $firstName;
- private $lastName;
- private $age;
-
- public function __construct($firstName, $lastName, $age) {
- $this->firstName = $firstName;
- $this->lastName = $lastName;
- $this->age = $age;
- }
-
+class User implements JsonI {
+ public function __construct(
+ private string $username,
+ private string $email
+ ) {}
+
public function toJSON(): Json {
- $json = new Json();
- $json->addString('first-name', $this->firstName);
- $json->addString('last-name', $this->lastName);
- $json->addNumber('age', $this->age);
-
- return $json;
+ return new Json(['username' => $this->username, 'email' => $this->email]);
}
}
$json = new Json();
-$person = new Person('Ibrahim', 'BinAlshikh', 30);
-$json->addObject('person', $person);
+$user = new User('ibrahim', 'ibrahim@example.com');
+$json->addObject('user', $user);
echo $json;
```
@@ -282,41 +162,34 @@ echo $json;
Output:
```json
-{
- "person":{
- "first-name":"Ibrahim",
- "last-name":"BinAlshikh",
- "age":30
- }
-}
+{"user":{"username":"ibrahim","email":"ibrahim@example.com"}}
```
### Auto-Mapping Objects
-If an object doesn't implement the `JsonI` interface, the library will try to map its public getter methods:
+For objects that don't implement `JsonI`, the library maps them automatically using two sources:
+
+1. **Public getter methods** — any method prefixed with `get` is called and its return value is added. The property name is the method name with `get` stripped (e.g. `getName()` → `Name`). Methods returning `null` or `false` are skipped.
+2. **Public properties** — extracted via reflection and added as-is, including those with a `null` value.
```php
-class User {
- private $username;
- private $email;
-
- public function __construct($username, $email) {
- $this->username = $username;
- $this->email = $email;
- }
-
- public function getUsername() {
- return $this->username;
- }
-
- public function getEmail() {
- return $this->email;
+class Product {
+ public string $sku = 'ABC-001'; // added via reflection
+ private string $name;
+ private float $price;
+
+ public function __construct(string $name, float $price) {
+ $this->name = $name;
+ $this->price = $price;
}
+
+ public function getName(): string { return $this->name; } // → "Name"
+ public function getPrice(): float { return $this->price; } // → "Price"
}
$json = new Json();
-$user = new User('ibrahimBin', 'ibrahim@example.com');
-$json->addObject('user', $user);
+$product = new Product('Keyboard', 49.99);
+$json->addObject('product', $product);
echo $json;
```
@@ -324,141 +197,76 @@ echo $json;
Output:
```json
-{
- "user":{
- "Username":"ibrahimBin",
- "Email":"ibrahim@example.com"
- }
-}
+{"product":{"Name":"Keyboard","Price":49.99,"sku":"ABC-001"}}
```
-## Decoding JSON String
-
-You can decode a JSON string into a `Json` object:
+## Property Naming Styles
-```php
-use WebFiori\Json\Json;
-
-$jsonString = '{"name":"Ibrahim","age":30,"city":"Riyadh"}';
-
-try {
- $jsonObj = Json::decode($jsonString);
-
- // Access properties
- echo $jsonObj->get('name'); // Outputs: Ibrahim
- echo $jsonObj->get('age'); // Outputs: 30
- echo $jsonObj->get('city'); // Outputs: Riyadh
-} catch (\WebFiori\Json\JsonException $ex) {
- echo 'Error: ' . $ex->getMessage();
-}
-```
-
-## Storing Output
+Four naming styles are supported: `none` (default), `camel`, `snake`, `kebab`.
+Three letter cases are supported: `same` (default), `upper`, `lower`.
-You can save the JSON output to a file:
+Set them in the constructor or change them later with `setPropsStyle()`:
```php
-use WebFiori\Json\Json;
+$data = ['first-name' => 'Ibrahim', 'last-name' => 'Al-Shikh'];
-$json = new Json([
- 'name' => 'Ibrahim',
- 'age' => 30,
- 'city' => 'Riyadh'
-]);
+echo new Json($data, 'none') . "\n"; // {"first-name":"Ibrahim","last-name":"Al-Shikh"}
+echo new Json($data, 'camel') . "\n"; // {"firstName":"Ibrahim","lastName":"Al-Shikh"}
+echo new Json($data, 'snake') . "\n"; // {"first_name":"Ibrahim","last_name":"Al-Shikh"}
+echo new Json($data, 'kebab') . "\n"; // {"first-name":"Ibrahim","last-name":"Al-Shikh"}
-try {
- $json->toJsonFile('data', '/path/to/directory', true);
- // This will create /path/to/directory/data.json
-
- echo 'File saved successfully!';
-} catch (\WebFiori\Json\JsonException $ex) {
- echo 'Error: ' . $ex->getMessage();
-}
+// Change style after construction
+$json = new Json($data);
+$json->setPropsStyle('snake', 'upper');
+echo $json . "\n"; // {"FIRST_NAME":"Ibrahim","LAST_NAME":"Al-Shikh"}
```
-## Working With Arrays
+## Decoding JSON
-You can add arrays to your JSON object:
+Decode a JSON string directly:
```php
-use WebFiori\Json\Json;
-
-$json = new Json();
-
-// Simple array
-$json->addArray('numbers', [1, 2, 3, 4, 5]);
+$json = Json::decode('{"name":"Ibrahim","age":30}');
-// Array of objects
-$json->addArray('users', [
- ['name' => 'Ibrahim', 'age' => 30],
- ['name' => 'Jane', 'age' => 25],
- ['name' => 'Bob', 'age' => 40]
-]);
-
-echo $json;
+echo $json->get('name'); // Ibrahim
+echo $json->get('age'); // 30
```
-Output:
+Read from a file:
-```json
-{
- "numbers":[1,2,3,4,5],
- "users":[
- {"name":"Ibrahim","age":30},
- {"name":"Jane","age":25},
- {"name":"Bob","age":40}
- ]
+```php
+try {
+ $json = Json::fromJsonFile('/path/to/file.json');
+ echo $json->get('someKey');
+} catch (\WebFiori\Json\JsonException $e) {
+ echo 'Error: ' . $e->getMessage();
}
```
-### Arrays as Objects
-
-You can also represent arrays as objects:
+## Saving to File
```php
-use WebFiori\Json\Json;
-
-$json = new Json();
+$json = new Json(['name' => 'Ibrahim', 'age' => 30]);
-$json->addArray('data', [
- 'name' => 'Ibrahim',
- 'age' => 30,
- 'skills' => ['PHP', 'JavaScript', 'Python']
-], true); // true means represent as object
-
-echo $json;
-```
-
-Output:
-
-```json
-{
- "data":{
- "name":"Ibrahim",
- "age":30,
- "skills":["PHP","JavaScript","Python"]
- }
+try {
+ $json->toJsonFile('data', '/path/to/directory', true);
+ // Creates /path/to/directory/data.json
+} catch (\WebFiori\Json\JsonException $e) {
+ echo 'Error: ' . $e->getMessage();
}
```
## JSONx
-JSONx is an IBM standard format that represents JSON as XML. The library supports converting JSON to JSONx:
+[JSONx](https://www.ibm.com/docs/en/datapower-gateways/10.0.1?topic=20-jsonx) is an IBM standard that represents JSON as XML:
```php
-use WebFiori\Json\Json;
-
$json = new Json([
- 'name' => 'Ibrahim',
- 'age' => 30,
+ 'name' => 'Ibrahim',
+ 'age' => 30,
'isEmployed' => true,
- 'address' => [
- 'city' => 'Riyadh',
- 'country' => 'Saudi Arabia'
- ]
]);
-// Output as JSONx
echo $json->toJSONxString();
```
@@ -466,67 +274,66 @@ Output:
```xml
-
- Ibrahim
- 30
- true
-
- Riyadh
- Saudi Arabia
-
+
+ Ibrahim
+
+
+ 30
+
+
+ true
+
```
## Error Handling
-The library uses the `JsonException` class for error handling:
+All errors throw `\WebFiori\Json\JsonException`:
```php
-use WebFiori\Json\Json;
-
try {
- // Attempt to decode invalid JSON
- $jsonObj = Json::decode('{invalid json}');
-} catch (\WebFiori\Json\JsonException $ex) {
- echo 'Error code: ' . $ex->getCode() . "\n";
- echo 'Error message: ' . $ex->getMessage();
+ $json = Json::decode('{invalid json}');
+} catch (\WebFiori\Json\JsonException $e) {
+ echo 'Error code: ' . $e->getCode() . "\n";
+ echo 'Error message: ' . $e->getMessage() . "\n";
}
```
## API Reference
-### Main Classes
-
-- **Json**: The main class for creating and manipulating JSON data
-- **JsonConverter**: Handles conversion between JSON and other formats
-- **Property**: Represents a property in a JSON object
-- **CaseConverter**: Utility for converting between different naming styles
-- **JsonI**: Interface for objects that can be converted to JSON
-- **JsonException**: Exception class for JSON-related errors
-- **JsonTypes**: Constants for JSON data types
-
-### Key Methods
-
-#### Json Class
-- `__construct(array $initialData = [], ?string $propsStyle = '', ?string $lettersCase = '', bool $isFormatted = false)`
-- `add(string $key, $value, $arrayAsObj = false): bool`
-- `addString(string $key, $val): bool`
-- `addNumber(string $key, $value): bool`
-- `addBoolean($key, $val = true): bool`
-- `addNull(string $key): bool`
-- `addArray(string $key, $value, $asObject = false): bool`
-- `addObject(string $key, &$val): bool`
-- `get($key): mixed`
-- `hasKey($key): bool`
-- `remove($keyName): ?Property`
-- `setPropsStyle(string $style, string $lettersCase = 'same'): void`
-- `setIsFormatted($bool): void`
-- `toJSONString(): string`
-- `toJSONxString(): string`
-- `toJsonFile(string $fileName, string $path, bool $override = false): void`
-
-#### Static Methods
-- `Json::decode($jsonStr): Json`
-- `Json::fromJsonFile($pathToJsonFile): Json`
+### Classes
+
+| Class | Description |
+|-------|-------------|
+| `Json` | Main class for building and reading JSON data |
+| `JsonI` | Interface for custom object serialization |
+| `JsonConverter` | Handles serialization to JSON and JSONx strings |
+| `Property` | Represents a single JSON property |
+| `CaseConverter` | Converts property names between naming styles |
+| `JsonTypes` | Constants for JSON data types |
+| `JsonException` | Exception thrown on JSON errors |
+
+### Key Methods — `Json`
+
+| Method | Description |
+|--------|-------------|
+| `add(string $key, mixed $value, bool $arrayAsObj = false): bool` | Add any value |
+| `addString(string $key, string $val): bool` | Add a string |
+| `addNumber(string $key, int\|float $value): bool` | Add a number |
+| `addBoolean(string $key, bool $val = true): bool` | Add a boolean |
+| `addNull(string $key): bool` | Add a null value |
+| `addArray(string $key, array $value, bool $asObject = false): bool` | Add an array |
+| `addObject(string $key, object &$val): bool` | Add an object |
+| `get(string $key): mixed` | Get a property value |
+| `hasKey(string $key): bool` | Check if a key exists |
+| `remove(string $key): ?Property` | Remove a property |
+| `setPropsStyle(string $style, string $lettersCase = 'same'): void` | Change naming style |
+| `setIsFormatted(bool $bool): void` | Toggle formatted output |
+| `toJSONString(): string` | Get JSON string |
+| `toJSONxString(): string` | Get JSONx string |
+| `toJsonFile(string $fileName, string $path, bool $override = false): void` | Save to file |
+| `Json::decode(string $jsonStr): Json` | Decode a JSON string |
+| `Json::fromJsonFile(string $path): Json` | Load from a JSON file |
diff --git a/WebFiori/Json/CaseConverter.php b/WebFiori/Json/CaseConverter.php
index aa5ce49..c06337e 100644
--- a/WebFiori/Json/CaseConverter.php
+++ b/WebFiori/Json/CaseConverter.php
@@ -1,4 +1,5 @@
getFirstProp() and getSecondProp().
- * In that case, the generated JSON will be on the format
- * {"FirstProp":"prop-1","SecondProp":""}.
- * This method also can be used to update the value of an existing property.
- *
+ * Adds an object to the JSON data.
+ *
+ * The object is serialized using the following rules:
+ *
+ * - If the object implements {@see JsonI}, its {@see JsonI::toJSON()} method
+ * is called to produce the JSON representation.
+ * - If the object is already an instance of {@see Json}, it is used directly.
+ * - Otherwise, all public getter methods (prefixed with 'get') are called and
+ * mapped to properties. The property name is the method name with the 'get' prefix
+ * removed (e.g.
getFirstProp() becomes FirstProp).
+ * Methods returning false or null are skipped.
+ * - Additionally, all public properties of the object are extracted via reflection
+ * and added to the JSON output. Public properties with a null value are included;
+ * private and protected properties are ignored.
+ *
+ * This method can also be used to update the value of an existing property.
+ *
* @param string $key The key value.
- *
+ *
* @param JsonI|Json|object $val The object that will be added.
- *
- * @return bool The method will return true if the object is added.
- * If the key value is invalid string, the method will return false.
- *
+ *
+ * @return bool True if the object is added, false if the key is invalid.
+ *
*/
public function addObject(string $key, &$val) {
if (!$this->updateExisting($key, $val)) {
@@ -755,6 +759,7 @@ private static function checkArray($subVal, &$parentArr) {
if ($isIndexed) {
$subArr = [];
+
// A sub array. Can have sub arrays.
// Sub arrays can have objects.
for ($x = 0 ; $x < count($subVal) ; $x++) {
diff --git a/WebFiori/Json/JsonConverter.php b/WebFiori/Json/JsonConverter.php
index 97978f4..cc75834 100644
--- a/WebFiori/Json/JsonConverter.php
+++ b/WebFiori/Json/JsonConverter.php
@@ -1,4 +1,5 @@
+ * If the object implements {@see JsonI}, its {@see JsonI::toJSON()} method is called.
+ * If the object is already an instance of {@see Json}, it is returned as-is.
+ * Otherwise, public getter methods (prefixed with 'get') are called and their return
+ * values are mapped to properties. The property name is derived by stripping the 'get'
+ * prefix (e.g. getFullName() becomes FullName). Methods that
+ * return false or null are skipped.
+ * Finally, all public properties are extracted via reflection and added to the JSON
+ * output. Unlike getter-based mapping, public properties with a null value are included.
+ *
*
* @author Ibrahim
- *
+ *
*/
class JsonConverter {
private static $CRLF = "\r\n";
@@ -23,25 +36,31 @@ class JsonConverter {
private static $TabSize = 0;
private static $XmlClosingPool = [];
/**
- * Convert an object to Json object.
- *
- * Note that the properties which will be in the generated Json
- * object will depend on the public 'get' methods of the object.
- * The name of the properties will depend on the name of the method. For
- * example, if the name of one of the methods is 'getFullName', then
- * property name will be 'FullName'.
- *
- * @param object $obj The object that will be converted.
- *
- * @return Json
+ * Converts a plain PHP object to a {@see Json} instance.
+ *
+ * The conversion follows this order:
+ *
+ * - If the object implements {@see JsonI}, its {@see JsonI::toJSON()} method is
+ * called and the result is returned directly.
+ * - If the object is already an instance of {@see Json}, it is returned as-is.
+ * - All public methods whose names start with 'get' are called. The portion of the
+ * method name after 'get' becomes the property name (e.g.
getFullName()
+ * produces the key FullName). Methods returning false or null are
+ * skipped.
+ * - All public properties are extracted via {@see \ReflectionClass} and added to
+ * the result. Properties with a null value are included; private and protected
+ * properties are ignored.
+ *
+ *
+ * @param object $obj The object to convert.
+ *
+ * @return Json A Json instance populated with the object's data.
*/
public static function objectToJson($obj) {
if (is_subclass_of($obj, 'Webfiori\\Json\\JsonI')) {
return $obj->toJSON();
- } else {
- if ($obj instanceof Json) {
- return $obj;
- }
+ } else if ($obj instanceof Json) {
+ return $obj;
}
$methods = get_class_methods($obj);
@@ -61,8 +80,18 @@ public static function objectToJson($obj) {
}
}
}
+
restore_error_handler();
+ $reflection = new \ReflectionClass($obj);
+ $publicProps = $reflection->getProperties(\ReflectionProperty::IS_PUBLIC);
+
+ foreach ($publicProps as $prop) {
+ $name = $prop->getName();
+ $value = $prop->getValue($obj);
+ $json->add($name, $value);
+ }
+
return $json;
}
/**
@@ -330,13 +359,18 @@ private static function getNumberVal($val) {
return $retVal;
}
/**
- *
- * @param object $probVal
- *
- * @param string $style
- *
- * @return string
- *
+ * Converts an object value to its JSON string representation.
+ *
+ * If the value is not already a {@see Json} instance and does not implement
+ * {@see JsonI}, it is first passed through {@see self::objectToJson()} which
+ * maps public getter methods and public properties via reflection.
+ *
+ * @param object $probVal The object to convert.
+ * @param string $style The property naming style to apply.
+ * @param string $lettersCase The letter case to apply to property names.
+ *
+ * @return string JSON object string representation.
+ *
* @since 1.0
*/
private static function objToJson($probVal, string $style, string $lettersCase) {
diff --git a/WebFiori/Json/JsonException.php b/WebFiori/Json/JsonException.php
index 0a22d48..47305de 100644
--- a/WebFiori/Json/JsonException.php
+++ b/WebFiori/Json/JsonException.php
@@ -1,4 +1,5 @@
=8.0"
+ "php": ">=8.1",
+ "ext-json": "*"
},
"autoload": {
"psr-4": {
@@ -24,9 +25,11 @@
},
"scripts": {
"test": "phpunit --configuration tests/phpunit.xml",
- "test-10": "phpunit --configuration tests/phpunit10.xml"
+ "test10": "phpunit --configuration tests/phpunit10.xml",
+ "fix-cs": "php-cs-fixer fix --config=php_cs.php.dist"
},
"require-dev": {
- "phpunit/phpunit": "^10.0"
+ "phpunit/phpunit": "^10.0",
+ "friendsofphp/php-cs-fixer": "^3.92"
}
-}
+}
diff --git a/examples/01-basic-usage.php b/examples/01-basic-usage.php
new file mode 100644
index 0000000..a3c71fa
--- /dev/null
+++ b/examples/01-basic-usage.php
@@ -0,0 +1,17 @@
+ 'Ibrahim',
+ 'age' => 30,
+ 'married' => false,
+ 'score' => 9.5,
+ 'notes' => null,
+]);
+
+echo $json."\n";
+// {"name":"Ibrahim","age":30,"married":false,"score":9.5,"notes":null}
diff --git a/examples/02-arrays.php b/examples/02-arrays.php
new file mode 100644
index 0000000..cfaa367
--- /dev/null
+++ b/examples/02-arrays.php
@@ -0,0 +1,12 @@
+addArray('tags', ['php', 'json', 'api']);
+$json->addArray('address', ['city' => 'Riyadh', 'country' => 'SA'], true); // true = as object
+
+echo $json."\n";
+// {"tags":["php","json","api"],"address":{"city":"Riyadh","country":"SA"}}
diff --git a/examples/03-object-with-jsoni.php b/examples/03-object-with-jsoni.php
new file mode 100644
index 0000000..05586dc
--- /dev/null
+++ b/examples/03-object-with-jsoni.php
@@ -0,0 +1,25 @@
+ $this->username, 'email' => $this->email]);
+ }
+}
+
+$json = new Json();
+$user = new User('ibrahim', 'ibrahim@example.com');
+$json->addObject('user', $user);
+
+echo $json."\n";
+// {"user":{"username":"ibrahim","email":"ibrahim@example.com"}}
diff --git a/examples/04-object-auto-mapping.php b/examples/04-object-auto-mapping.php
new file mode 100644
index 0000000..0d8ac24
--- /dev/null
+++ b/examples/04-object-auto-mapping.php
@@ -0,0 +1,31 @@
+name = $name;
+ $this->price = $price;
+ }
+
+ public function getName(): string {
+ return $this->name;
+ }
+ public function getPrice(): float {
+ return $this->price;
+ }
+}
+
+$json = new Json();
+$product = new Product('Keyboard', 49.99);
+$json->addObject('product', $product);
+
+echo $json."\n";
+// {"product":{"Name":"Keyboard","Price":49.99,"sku":"ABC-001"}}
+// ^ getters produce "Name"/"Price", public property adds "sku"
diff --git a/examples/05-naming-styles.php b/examples/05-naming-styles.php
new file mode 100644
index 0000000..4563217
--- /dev/null
+++ b/examples/05-naming-styles.php
@@ -0,0 +1,17 @@
+ 'Ibrahim', 'last-name' => 'Al-Shikh'];
+
+echo new Json($data, 'none')."\n"; // {"first-name":"Ibrahim","last-name":"Al-Shikh"}
+echo new Json($data, 'camel')."\n"; // {"firstName":"Ibrahim","lastName":"Al-Shikh"}
+echo new Json($data, 'snake')."\n"; // {"first_name":"Ibrahim","last_name":"Al-Shikh"}
+echo new Json($data, 'kebab')."\n"; // {"first-name":"Ibrahim","last-name":"Al-Shikh"}
+
+// Change style after construction + apply upper case
+$json = new Json($data);
+$json->setPropsStyle('snake', 'upper');
+echo $json."\n"; // {"FIRST_NAME":"Ibrahim","LAST_NAME":"Al-Shikh"}
diff --git a/examples/06-decode-and-read.php b/examples/06-decode-and-read.php
new file mode 100644
index 0000000..e4b7fc0
--- /dev/null
+++ b/examples/06-decode-and-read.php
@@ -0,0 +1,21 @@
+get('name')."\n"; // Ibrahim
+echo $json->get('age')."\n"; // 30
+
+// Read from file
+try {
+ $json = Json::fromJsonFile(__DIR__.'/sample.json');
+ echo $json->get('product')."\n"; // Keyboard
+ echo $json->get('price')."\n"; // 49.99
+ echo ($json->get('inStock') ? 'true' : 'false')."\n"; // true
+} catch (JsonException $e) {
+ echo 'Error: '.$e->getMessage()."\n";
+}
diff --git a/examples/07-jsonx.php b/examples/07-jsonx.php
new file mode 100644
index 0000000..33117be
--- /dev/null
+++ b/examples/07-jsonx.php
@@ -0,0 +1,13 @@
+ 'Ibrahim',
+ 'age' => 30,
+ 'isEmployed' => true,
+]);
+
+echo $json->toJSONxString()."\n";
diff --git a/examples/README.md b/examples/README.md
new file mode 100644
index 0000000..f4ae564
--- /dev/null
+++ b/examples/README.md
@@ -0,0 +1,17 @@
+# Examples
+
+| File | What it shows |
+|------|---------------|
+| [01-basic-usage.php](01-basic-usage.php) | Constructor init, scalar types, `echo` output |
+| [02-arrays.php](02-arrays.php) | Indexed arrays, associative arrays as objects |
+| [03-object-with-jsoni.php](03-object-with-jsoni.php) | Custom serialization via the `JsonI` interface |
+| [04-object-auto-mapping.php](04-object-auto-mapping.php) | Auto-mapping via public getters and public properties |
+| [05-naming-styles.php](05-naming-styles.php) | `camel` / `snake` / `kebab` / `none` styles + letter case |
+| [06-decode-and-read.php](06-decode-and-read.php) | Decoding JSON strings and reading from files |
+| [07-jsonx.php](07-jsonx.php) | Converting JSON to JSONx (XML) format |
+
+Run any example from the project root:
+
+```bash
+php examples/01-basic-usage.php
+```
diff --git a/examples/basic-usage.php b/examples/basic-usage.php
deleted file mode 100644
index aa8b4f6..0000000
--- a/examples/basic-usage.php
+++ /dev/null
@@ -1,35 +0,0 @@
-addNumber('my-number', 34);
-
-//add a boolean with 'false' as its value.
-$j->addBoolean('my-boolean', false);
-
-//add a string
-$j->addString('a-string', 'Hello, I\'m JsonX! I like "JSON". ');
-
-header('content-type:application/json');
-/*
-send back the generated json string.
-The output of the code will be like that:
-{
- "my-number":34,
- "my-boolean":false,
- "my-number":"Hello, I'm JsonX! I like \"json\". ",
-}
-*/
-echo $j;
diff --git a/examples/sample.json b/examples/sample.json
new file mode 100644
index 0000000..3b5bc81
--- /dev/null
+++ b/examples/sample.json
@@ -0,0 +1,5 @@
+{
+ "product": "Keyboard",
+ "price": 49.99,
+ "inStock": true
+}
diff --git a/examples/use-with-arrays.php b/examples/use-with-arrays.php
deleted file mode 100644
index 5764f35..0000000
--- a/examples/use-with-arrays.php
+++ /dev/null
@@ -1,33 +0,0 @@
- 'Ibrahim',100,
- 'age' => '25','sex' => 'M',true,false
-];
-
-//add the array as is
-$j->add('basic-array', $arr);
-
-//adding the array as object
-$j->addArray('basic-array-as-object', $arr,true);
-
-//adding the array as is
-$j->add('complicated-array', $arr2);
-
-//adding the array as object
-$j->addArray('complicated-array-as-object', $arr2,true);
-echo $j;
diff --git a/examples/use-with-classes-2.php b/examples/use-with-classes-2.php
deleted file mode 100644
index fc18edf..0000000
--- a/examples/use-with-classes-2.php
+++ /dev/null
@@ -1,43 +0,0 @@
-email;
- }
- public function getUserName() {
- return $this->username;
- }
-
- public function setEmail($email) {
- $this->email = $email;
- }
- public function setUsername($username) {
- $this->username = $username;
- }
-}
-
-$user = new User();
-$user->setEmail('example@example.com');
-$user->setUsername('Warrior Vx');
-$json = new Json();
-$json->addObject('user', $user);
-header('content-type:application/json');
-echo $json;
diff --git a/examples/use-with-classes.php b/examples/use-with-classes.php
deleted file mode 100644
index 973217b..0000000
--- a/examples/use-with-classes.php
+++ /dev/null
@@ -1,55 +0,0 @@
-username;
- }
-
- public function setEmail($email) {
- $this->email = $email;
- }
- public function setUsername($username) {
- $this->username = $username;
- }
-
- //this function is from the interface JsonI
- // it must return JsonX object
- public function toJSON() {
- $retVal = new Json();
- $retVal->addString('username', $this->username);
- $retVal->addString('email', $this->email);
-
- return $retVal;
- }
-}
-$user = new User();
-$user->setEmail('example@example.com');
-$user->setUsername('Warrior Vx');
-
-$json = new JsonX();
-$json->addBoolean('my-boolean');
-$json->addObject('user', $user);
-
-//adding arrays
-$json->addArray('my-arr', [1,2,"hello",["nested array"]]);
-
-header('content-type:application/json');
-//display json object in the browser.
-echo $json;
diff --git a/php_cs.php.dist b/php_cs.php.dist
index ac0a570..0b4f088 100644
--- a/php_cs.php.dist
+++ b/php_cs.php.dist
@@ -11,7 +11,7 @@ return $config->setRules([
'align_multiline_comment' => [
'comment_type' => 'phpdocs_only'
],
- 'array_indentation' => [],
+ 'array_indentation' => true,
'array_syntax' => [
'syntax' => 'short'
],
diff --git a/tests/WebFiori/Tests/Json/JsonConverterTest.php b/tests/WebFiori/Tests/Json/JsonConverterTest.php
index e874f26..3648e0e 100644
--- a/tests/WebFiori/Tests/Json/JsonConverterTest.php
+++ b/tests/WebFiori/Tests/Json/JsonConverterTest.php
@@ -5,6 +5,7 @@
use WebFiori\Json\Json;
use WebFiori\Tests\Obj0;
use WebFiori\Tests\Obj1;
+use WebFiori\Tests\ObjWithPublicProps;
use PHPUnit\Framework\TestCase;
use WebFiori\Json\Property;
use WebFiori\Json\JsonConverter;
@@ -76,4 +77,44 @@ public function testPropertyToJsonXString01() {
. ' world'."\r\n"
. ''."\r\n", JsonConverter::propertyToJsonXString($prop, false));
}
+
+ /**
+ * @test
+ */
+ public function testObjectToJsonPublicPropsBasic() {
+ $obj = new \WebFiori\Tests\ObjWithPublicProps('Ibrahim', 30, true);
+ $json = JsonConverter::objectToJson($obj);
+ $this->assertTrue($json instanceof Json);
+ $this->assertTrue($json->hasKey('name'));
+ $this->assertTrue($json->hasKey('age'));
+ $this->assertTrue($json->hasKey('active'));
+ }
+ /**
+ * @test
+ */
+ public function testObjectToJsonPublicPropsValues() {
+ $obj = new \WebFiori\Tests\ObjWithPublicProps('Ibrahim', 30, true);
+ $json = JsonConverter::objectToJson($obj);
+ $this->assertEquals('Ibrahim', $json->get('name'));
+ $this->assertEquals(30, $json->get('age'));
+ $this->assertEquals(true, $json->get('active'));
+ }
+ /**
+ * @test
+ */
+ public function testObjectToJsonPrivatePropsNotIncluded() {
+ $obj = new \WebFiori\Tests\ObjWithPublicProps('Ibrahim', 30, true);
+ $json = JsonConverter::objectToJson($obj);
+ $this->assertFalse($json->hasKey('secret'));
+ }
+ /**
+ * @test
+ */
+ public function testObjectToJsonPublicPropsNullValue() {
+ $obj = new \WebFiori\Tests\ObjWithPublicProps('Ibrahim', 30, true);
+ $obj->name = null;
+ $json = JsonConverter::objectToJson($obj);
+ $this->assertTrue($json->hasKey('name'));
+ $this->assertNull($json->get('name'));
+ }
}
diff --git a/tests/WebFiori/Tests/ObjWithPublicProps.php b/tests/WebFiori/Tests/ObjWithPublicProps.php
new file mode 100644
index 0000000..e23a51f
--- /dev/null
+++ b/tests/WebFiori/Tests/ObjWithPublicProps.php
@@ -0,0 +1,15 @@
+name = $name;
+ $this->age = $age;
+ $this->active = $active;
+ }
+}