Skip to content
This repository was archived by the owner on Jan 30, 2020. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ All notable changes to this project will be documented in this file, in reverse

### Added

- Nothing.
- forceObject flag to the encoder.

### Changed

Expand Down
16 changes: 16 additions & 0 deletions docs/book/advanced.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,22 @@ $jsonObject = Zend\Json\Json::encode(
);
```

## Forcing arrays to be JSON objects

If you need the JSON to produce objects in all cases, you may pass the
option `forceObject` in the encode options to force the encoding to
objects only. This often makes the resultant JSON string longer in cases
where the encoding would have automatically chosen an array: however,
the benefit is that this produces a consistent schema.

```php
$jsonObject = Zend\Json\Json::encode(
$data,
true,
['forceObject' => true]
);
```

## Internal Encoder/Decoder

`Zend\Json` has two different modes depending if ext/json is enabled in your PHP
Expand Down
14 changes: 14 additions & 0 deletions src/Encoder.php
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,17 @@ public static function encode($value, $cycleCheck = false, array $options = [])
return $encoder->encodeValue($value);
}

/**
* Discover whether the force-object option flag is set, which would
* imply this encoder should force arrays to be objects.
*
* @return bool the encode should force arrays to be objects.
*/
protected function isForceObjectSet()
{
return isset($this->options['forceObject']) && $this->options['forceObject'];
}

/**
* Encode a value to JSON.
*
Expand Down Expand Up @@ -189,6 +200,9 @@ protected function wasVisited(&$value)
*/
protected function encodeArray($array)
{
if ($this->isForceObjectSet()) {
return $this->encodeAssociativeArray($array);
}
// Check for associative array
if (! empty($array) && (array_keys($array) !== range(0, count($array) - 1))) {
// Associative array
Expand Down
12 changes: 10 additions & 2 deletions src/Json.php
Original file line number Diff line number Diff line change
Expand Up @@ -293,7 +293,8 @@ private static function decodeViaPhpBuiltIn($encodedValue, $objectDecodeType)
private static function encodeValue($valueToEncode, $cycleCheck, array $options, $prettyPrint)
{
if (function_exists('json_encode') && static::$useBuiltinEncoderDecoder !== true) {
return self::encodeViaPhpBuiltIn($valueToEncode, $prettyPrint);
$forceObject = (isset($options['forceObject']) && ($options['forceObject'] === true));
return self::encodeViaPhpBuiltIn($valueToEncode, $prettyPrint, $forceObject);
}

return self::encodeViaEncoder($valueToEncode, $cycleCheck, $options, $prettyPrint);
Expand All @@ -311,12 +312,15 @@ private static function encodeValue($valueToEncode, $cycleCheck, array $options,
*
* If $prettyPrint is boolean true, also uses JSON_PRETTY_PRINT.
*
* If $forceObject is boolean true, also uses JSON_FORCE_OBJECT.
*
* @param mixed $valueToEncode
* @param bool $prettyPrint
* @param bool $forceObject
* @return string|false Boolean false return value if json_encode is not
* available, or the $useBuiltinEncoderDecoder flag is enabled.
*/
private static function encodeViaPhpBuiltIn($valueToEncode, $prettyPrint = false)
private static function encodeViaPhpBuiltIn($valueToEncode, $prettyPrint = false, $forceObject = false)
{
if (! function_exists('json_encode') || static::$useBuiltinEncoderDecoder === true) {
return false;
Expand All @@ -328,6 +332,10 @@ private static function encodeViaPhpBuiltIn($valueToEncode, $prettyPrint = false
$encodeOptions |= JSON_PRETTY_PRINT;
}

if ($forceObject) {
$encodeOptions |= JSON_FORCE_OBJECT;
}

return json_encode($valueToEncode, $encodeOptions);
}

Expand Down
174 changes: 172 additions & 2 deletions test/JsonTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -1010,8 +1010,8 @@ public function testPrettyPrintEmptyPropertiesWithWhitespace()

],
"bar": {


}
}
JSON;
Expand Down Expand Up @@ -1142,4 +1142,174 @@ public function testWillDecodeStructureWithEmptyKeyToObjectProperly()
$object = Json\Json::decode($json, Json\Json::TYPE_OBJECT);
$this->assertAttributeEquals('test', '_empty_', $object);
}

/**
* Get consistent input data to test the four possible encoding paths:
* encoder: BuiltIn , forceObject: false
* encoder: BuiltIn , forceObject: true
* encoder: Encoder , forceObject: false
* encoder: Encoder , forceObject: true
*
* @see getExpectedForceObjectTestResult
* @see getExpectedNotForcedObjectTestResult
* @see testForceObjectWithBuiltInEncoder
* @see testForceObjectWithEncoderComponent
* @see testNotForcedObjectWithBuiltInEncoder
* @see testNotForcedObjectWithEncoderComponent
* @return array known consistent forceObject test data
*/
private function getForceObjectTestData()
{
$source = [
0 => 'zero',
1 => 'one',
2 => 'two',
3 => 'three',
4 => [
100 => 'one hundred',
200 => 'two hundred',
300 => 'three hundred',
],
5 => [
'a','b','c','d','e',
],
6 => [
0,1,2,3,4,5,
],
7 => [],
8 => [
"a" => [1],
],
];

return $source;
}

/**
* Get expected output of
* encoder: BuiltIn , forceObject: true
* encoder: Encoder , forceObject: true
*
* @see getForceObjectTestData
* @see testForceObjectWithBuiltInEncoder
* @see testForceObjectWithEncoderComponent
* @return string expected JSON encoded string of the data from {@see JsonTest::getForceObjectTestData()}.
*/
private function getExpectedForceObjectTestResult()
{
$expected = '{"0":"zero","1":"one","2":"two","3":"three",'
. '"4":{"100":"one hundred","200":"two hundred","300":"three hundred"},'
. '"5":{"0":"a","1":"b","2":"c","3":"d","4":"e"},'
. '"6":{"0":0,"1":1,"2":2,"3":3,"4":4,"5":5},"7":{},"8":{"a":{"0":1}}}';

return $expected;
}

/**
* Get expected output of
* encoder: BuiltIn , forceObject: false
* encoder: Encoder , forceObject: false
*
* @see getForceObjectTestData
* @see testNotForcedObjectWithBuiltInEncoder
* @see testNotForcedObjectWithEncoderComponent
* @return string expected JSON encoded string of the data from {@see JsonTest::getForceObjectTestData()}.
*/
private function getExpectedNotForcedObjectTestResult()
{
$expected = '["zero","one","two","three",{"100":"one hundred","200":"two hundred","300":"three hundred"},'
. '["a","b","c","d","e"],[0,1,2,3,4,5],[],{"a":[1]}]';

return $expected;
}

/**
* Test built in encoder when setting the forceObject flag.
*/
public function testForceObjectWithBuiltInEncoder()
{
Json\Json::$useBuiltinEncoderDecoder = true;

$source = $this->getForceObjectTestData();
$expected = $this->getExpectedForceObjectTestResult();

$actual = Json\Json::encode($source, false, ["forceObject" => true]);

$this->assertEquals($expected, $actual);
}

/**
* Test Encoder class when setting the forceObject flag.
*/
public function testForceObjectWithEncoderComponent()
{
Json\Json::$useBuiltinEncoderDecoder = false;

$source = $this->getForceObjectTestData();
$expected = $this->getExpectedForceObjectTestResult();

$actual = Json\Json::encode($source, false, ["forceObject" => true]);

$this->assertEquals($expected, $actual);
}

/**
* Test built in encoder when clearing the forceObject flag.
*/
public function testNotForcedObjectWithBuiltInEncoder()
{
Json\Json::$useBuiltinEncoderDecoder = true;

$source = $this->getForceObjectTestData();
$expected = $this->getExpectedNotForcedObjectTestResult();

$actual = Json\Json::encode($source, false, ["forceObject" => false]);

$this->assertEquals($expected, $actual);
}

/**
* Test Encoder class when clearing the forceObject flag.
*/
public function testNotForcedObjectWithEncoderComponent()
{
Json\Json::$useBuiltinEncoderDecoder = false;

$source = $this->getForceObjectTestData();
$expected = $this->getExpectedNotForcedObjectTestResult();

$actual = Json\Json::encode($source, false, ["forceObject" => false]);

$this->assertEquals($expected, $actual);
}

/**
* Test built in encoder when utilising the default forceObject flag
*/
public function testDefaultForceObjectWithBuiltInEncoder()
{
Json\Json::$useBuiltinEncoderDecoder = true;

$source = $this->getForceObjectTestData();
$expected = $this->getExpectedNotForcedObjectTestResult();

$actual = Json\Json::encode($source);

$this->assertEquals($expected, $actual);
}

/**
* Test Encoder class when utilising the default forceObject flag
*/
public function testDefaultForceObjectWithEncoderComponent()
{
Json\Json::$useBuiltinEncoderDecoder = false;

$source = $this->getForceObjectTestData();
$expected = $this->getExpectedNotForcedObjectTestResult();

$actual = Json\Json::encode($source);

$this->assertEquals($expected, $actual);
}
}