Skip to content

Commit e8222f3

Browse files
committed
feat: Added Support for Converting Public Props
1 parent 9cbeff8 commit e8222f3

5 files changed

Lines changed: 152 additions & 50 deletions

File tree

WebFiori/Json/Json.php

Lines changed: 21 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -358,26 +358,29 @@ public function addNumber(string $key, $value) {
358358
return true;
359359
}
360360
/**
361-
* Adds an object to the JSON string.
362-
*
363-
* The object that will be added can implement the interface JsonI to make
364-
* the generated JSON string customizable. Also, the object can be of
365-
* type Json. If the given value is an object that does not implement the
366-
* interface JsonI, or it is not of type Json,
367-
* The method will try to extract object information based on its "getXxxxx()" public
368-
* methods. Assuming that the object has 2 public methods with names
369-
* <code>getFirstProp()</code> and <code>getSecondProp()</code>.
370-
* In that case, the generated JSON will be on the format
371-
* <b>{"FirstProp":"prop-1","SecondProp":""}</b>.
372-
* This method also can be used to update the value of an existing property.
373-
*
361+
* Adds an object to the JSON data.
362+
*
363+
* The object is serialized using the following rules:
364+
* <ul>
365+
* <li>If the object implements {@see JsonI}, its {@see JsonI::toJSON()} method
366+
* is called to produce the JSON representation.</li>
367+
* <li>If the object is already an instance of {@see Json}, it is used directly.</li>
368+
* <li>Otherwise, all public getter methods (prefixed with 'get') are called and
369+
* mapped to properties. The property name is the method name with the 'get' prefix
370+
* removed (e.g. <code>getFirstProp()</code> becomes <code>FirstProp</code>).
371+
* Methods returning false or null are skipped.</li>
372+
* <li>Additionally, all public properties of the object are extracted via reflection
373+
* and added to the JSON output. Public properties with a null value are included;
374+
* private and protected properties are ignored.</li>
375+
* </ul>
376+
* This method can also be used to update the value of an existing property.
377+
*
374378
* @param string $key The key value.
375-
*
379+
*
376380
* @param JsonI|Json|object $val The object that will be added.
377-
*
378-
* @return bool The method will return true if the object is added.
379-
* If the key value is invalid string, the method will return false.
380-
*
381+
*
382+
* @return bool True if the object is added, false if the key is invalid.
383+
*
381384
*/
382385
public function addObject(string $key, &$val) {
383386
if (!$this->updateExisting($key, $val)) {

WebFiori/Json/JsonConverter.php

Lines changed: 57 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,22 @@
1111
namespace WebFiori\Json;
1212

1313
/**
14-
* A class to convert Json instance to it's JSON string representation.
14+
* A class to convert objects and Json instances to their JSON string representation.
15+
*
16+
* The conversion of plain objects follows this order of precedence:
17+
* <ol>
18+
* <li>If the object implements {@see JsonI}, its {@see JsonI::toJSON()} method is called.</li>
19+
* <li>If the object is already an instance of {@see Json}, it is returned as-is.</li>
20+
* <li>Otherwise, public getter methods (prefixed with 'get') are called and their return
21+
* values are mapped to properties. The property name is derived by stripping the 'get'
22+
* prefix (e.g. <code>getFullName()</code> becomes <code>FullName</code>). Methods that
23+
* return false or null are skipped.</li>
24+
* <li>Finally, all public properties are extracted via reflection and added to the JSON
25+
* output. Unlike getter-based mapping, public properties with a null value are included.</li>
26+
* </ol>
1527
*
1628
* @author Ibrahim
17-
*
29+
*
1830
*/
1931
class JsonConverter {
2032
private static $CRLF = "\r\n";
@@ -23,25 +35,31 @@ class JsonConverter {
2335
private static $TabSize = 0;
2436
private static $XmlClosingPool = [];
2537
/**
26-
* Convert an object to Json object.
27-
*
28-
* Note that the properties which will be in the generated Json
29-
* object will depend on the public 'get' methods of the object.
30-
* The name of the properties will depend on the name of the method. For
31-
* example, if the name of one of the methods is 'getFullName', then
32-
* property name will be 'FullName'.
33-
*
34-
* @param object $obj The object that will be converted.
35-
*
36-
* @return Json
38+
* Converts a plain PHP object to a {@see Json} instance.
39+
*
40+
* The conversion follows this order:
41+
* <ol>
42+
* <li>If the object implements {@see JsonI}, its {@see JsonI::toJSON()} method is
43+
* called and the result is returned directly.</li>
44+
* <li>If the object is already an instance of {@see Json}, it is returned as-is.</li>
45+
* <li>All public methods whose names start with 'get' are called. The portion of the
46+
* method name after 'get' becomes the property name (e.g. <code>getFullName()</code>
47+
* produces the key <code>FullName</code>). Methods returning false or null are
48+
* skipped.</li>
49+
* <li>All public properties are extracted via {@see \ReflectionClass} and added to
50+
* the result. Properties with a null value are included; private and protected
51+
* properties are ignored.</li>
52+
* </ol>
53+
*
54+
* @param object $obj The object to convert.
55+
*
56+
* @return Json A Json instance populated with the object's data.
3757
*/
3858
public static function objectToJson($obj) {
3959
if (is_subclass_of($obj, 'Webfiori\\Json\\JsonI')) {
4060
return $obj->toJSON();
41-
} else {
42-
if ($obj instanceof Json) {
43-
return $obj;
44-
}
61+
} else if ($obj instanceof Json) {
62+
return $obj;
4563
}
4664

4765
$methods = get_class_methods($obj);
@@ -61,8 +79,18 @@ public static function objectToJson($obj) {
6179
}
6280
}
6381
}
82+
6483
restore_error_handler();
6584

85+
$reflection = new \ReflectionClass($obj);
86+
$publicProps = $reflection->getProperties(\ReflectionProperty::IS_PUBLIC);
87+
88+
foreach ($publicProps as $prop) {
89+
$name = $prop->getName();
90+
$value = $prop->getValue($obj);
91+
$json->add($name, $value);
92+
}
93+
6694
return $json;
6795
}
6896
/**
@@ -330,13 +358,18 @@ private static function getNumberVal($val) {
330358
return $retVal;
331359
}
332360
/**
333-
*
334-
* @param object $probVal
335-
*
336-
* @param string $style
337-
*
338-
* @return string
339-
*
361+
* Converts an object value to its JSON string representation.
362+
*
363+
* If the value is not already a {@see Json} instance and does not implement
364+
* {@see JsonI}, it is first passed through {@see self::objectToJson()} which
365+
* maps public getter methods and public properties via reflection.
366+
*
367+
* @param object $probVal The object to convert.
368+
* @param string $style The property naming style to apply.
369+
* @param string $lettersCase The letter case to apply to property names.
370+
*
371+
* @return string JSON object string representation.
372+
*
340373
* @since 1.0
341374
*/
342375
private static function objToJson($probVal, string $style, string $lettersCase) {

WebFiori/Json/JsonI.php

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,18 +11,28 @@
1111
namespace WebFiori\Json;
1212

1313
/**
14-
* An interface for the objects that can be added to an instance of Json.
15-
* @author Ibrahim
14+
* Interface for objects that provide a custom JSON representation.
15+
*
16+
* Implementing this interface allows a class to control exactly how it is
17+
* serialized when passed to {@see Json::addObject()} or
18+
* {@see JsonConverter::objectToJson()}. When either of those methods encounters
19+
* an object that implements this interface, they call {@see JsonI::toJSON()}
20+
* instead of falling back to getter-method or reflection-based mapping.
21+
*
22+
* @author Ibrahim
1623
* @see Json
24+
* @see JsonConverter
1725
*/
1826
interface JsonI {
1927
/**
20-
* Returns an object of type Json.
21-
* This method can be implemented by any class that will be added
22-
* to any Json instance. It is used to customize the generated
23-
* JSON string.
24-
*
25-
* @return Json An instance of Json.
28+
* Returns a {@see Json} instance that represents the object.
29+
*
30+
* Implement this method to define which properties are included in the
31+
* JSON output and how they are named. The returned instance will be used
32+
* directly by {@see Json::addObject()} and {@see JsonConverter::objectToJson()},
33+
* bypassing getter-method scanning and reflection-based property extraction.
34+
*
35+
* @return Json A Json instance representing this object.
2636
*/
2737
public function toJSON() : Json;
2838
}

tests/WebFiori/Tests/Json/JsonConverterTest.php

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
use WebFiori\Json\Json;
66
use WebFiori\Tests\Obj0;
77
use WebFiori\Tests\Obj1;
8+
use WebFiori\Tests\ObjWithPublicProps;
89
use PHPUnit\Framework\TestCase;
910
use WebFiori\Json\Property;
1011
use WebFiori\Json\JsonConverter;
@@ -76,4 +77,44 @@ public function testPropertyToJsonXString01() {
7677
. ' world'."\r\n"
7778
. '</json:string>'."\r\n", JsonConverter::propertyToJsonXString($prop, false));
7879
}
80+
81+
/**
82+
* @test
83+
*/
84+
public function testObjectToJsonPublicPropsBasic() {
85+
$obj = new \WebFiori\Tests\ObjWithPublicProps('Ibrahim', 30, true);
86+
$json = JsonConverter::objectToJson($obj);
87+
$this->assertTrue($json instanceof Json);
88+
$this->assertTrue($json->hasKey('name'));
89+
$this->assertTrue($json->hasKey('age'));
90+
$this->assertTrue($json->hasKey('active'));
91+
}
92+
/**
93+
* @test
94+
*/
95+
public function testObjectToJsonPublicPropsValues() {
96+
$obj = new \WebFiori\Tests\ObjWithPublicProps('Ibrahim', 30, true);
97+
$json = JsonConverter::objectToJson($obj);
98+
$this->assertEquals('Ibrahim', $json->get('name'));
99+
$this->assertEquals(30, $json->get('age'));
100+
$this->assertEquals(true, $json->get('active'));
101+
}
102+
/**
103+
* @test
104+
*/
105+
public function testObjectToJsonPrivatePropsNotIncluded() {
106+
$obj = new \WebFiori\Tests\ObjWithPublicProps('Ibrahim', 30, true);
107+
$json = JsonConverter::objectToJson($obj);
108+
$this->assertFalse($json->hasKey('secret'));
109+
}
110+
/**
111+
* @test
112+
*/
113+
public function testObjectToJsonPublicPropsNullValue() {
114+
$obj = new \WebFiori\Tests\ObjWithPublicProps('Ibrahim', 30, true);
115+
$obj->name = null;
116+
$json = JsonConverter::objectToJson($obj);
117+
$this->assertTrue($json->hasKey('name'));
118+
$this->assertNull($json->get('name'));
119+
}
79120
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<?php
2+
namespace WebFiori\Tests;
3+
4+
class ObjWithPublicProps {
5+
public $name;
6+
public $age;
7+
public $active;
8+
private $secret = 'hidden';
9+
10+
public function __construct($name, $age, $active) {
11+
$this->name = $name;
12+
$this->age = $age;
13+
$this->active = $active;
14+
}
15+
}

0 commit comments

Comments
 (0)