diff --git a/src/parser/PhutilJSON.php b/src/parser/PhutilJSON.php index 0bcc4f76..5f43a625 100644 --- a/src/parser/PhutilJSON.php +++ b/src/parser/PhutilJSON.php @@ -1,155 +1,161 @@ encodeFormattedObject($object, 0)."\n"; } /** * Encode a list in JSON and pretty-print it, discarding keys. * * @param list List to encode in JSON. * @return string Pretty-printed list representation. */ public function encodeAsList(array $list) { return $this->encodeFormattedArray($list, 0)."\n"; } /* -( Internals )---------------------------------------------------------- */ /** * Pretty-print a JSON object. * * @param dict Object to format. * @param int Current depth, for indentation. * @return string Pretty-printed value. * @task internal */ private function encodeFormattedObject($object, $depth) { + if ($object instanceof stdClass) { + $object = (array)$object; + } + if (empty($object)) { return '{}'; } $pre = $this->getIndent($depth); $key_pre = $this->getIndent($depth + 1); $keys = array(); $vals = array(); $max = 0; foreach ($object as $key => $val) { $ekey = $this->encodeFormattedValue((string)$key, 0); $max = max($max, strlen($ekey)); $keys[] = $ekey; $vals[] = $this->encodeFormattedValue($val, $depth + 1); } $key_lines = array(); foreach ($keys as $k => $key) { $key_lines[] = $key_pre.$key.': '.$vals[$k]; } $key_lines = implode(",\n", $key_lines); $out = "{\n"; $out .= $key_lines; $out .= "\n"; $out .= $pre.'}'; return $out; } /** * Pretty-print a JSON list. * * @param list List to format. * @param int Current depth, for indentation. * @return string Pretty-printed value. * @task internal */ private function encodeFormattedArray($array, $depth) { if (empty($array)) { return '[]'; } $pre = $this->getIndent($depth); $val_pre = $this->getIndent($depth + 1); $vals = array(); foreach ($array as $val) { $vals[] = $val_pre.$this->encodeFormattedValue($val, $depth + 1); } $val_lines = implode(",\n", $vals); $out = "[\n"; $out .= $val_lines; $out .= "\n"; $out .= $pre.']'; return $out; } /** * Pretty-print a JSON value. * * @param dict Value to format. * @param int Current depth, for indentation. * @return string Pretty-printed value. * @task internal */ private function encodeFormattedValue($value, $depth) { if (is_array($value)) { if (phutil_is_natural_list($value)) { return $this->encodeFormattedArray($value, $depth); } else { return $this->encodeFormattedObject($value, $depth); } + } else if (is_object($value)) { + return $this->encodeFormattedObject($value, $depth); } else { if (defined('JSON_UNESCAPED_SLASHES')) { // If we have a new enough version of PHP, disable escaping of slashes // when pretty-printing values. Escaping slashes can defuse an attack // where the attacker embeds "" inside a JSON string, but that // isn't relevant when rendering JSON for human viewers. return json_encode($value, JSON_UNESCAPED_SLASHES); } else { return json_encode($value); } } } /** * Render a string corresponding to the current indent depth. * * @param int Current depth. * @return string Indentation. * @task internal */ private function getIndent($depth) { if (!$depth) { return ''; } else { return str_repeat(' ', $depth); } } } diff --git a/src/parser/__tests__/PhutilJSONTestCase.php b/src/parser/__tests__/PhutilJSONTestCase.php index 295d7b55..63630a9d 100644 --- a/src/parser/__tests__/PhutilJSONTestCase.php +++ b/src/parser/__tests__/PhutilJSONTestCase.php @@ -1,21 +1,50 @@ assertEqual( $expect, $serializer->encodeFormatted(array('x' => array())), pht('Empty arrays should serialize as `%s`, not `%s`.', '[]', '{}')); } + public function testNestedObjectEncoding() { + $expect = <<duck = 'quack'; + + $input = (object)array( + 'empty-object' => $empty_object, + 'pair-object' => $pair_object, + ); + + $serializer = new PhutilJSON(); + + $this->assertEqual( + $expect, + $serializer->encodeFormatted($input), + pht('Serialization of PHP-object JSON values.')); + } + }