1<?php 2 3/** 4 * Utilities for wrangling JSON. 5 * 6 * @task pretty Formatting JSON Objects 7 * @task internal Internals 8 */ 9final class PhutilJSON extends Phobject { 10 11 12/* -( Formatting JSON Objects )-------------------------------------------- */ 13 14 15 /** 16 * Encode an object in JSON and pretty-print it. This generates a valid JSON 17 * object with human-readable whitespace and indentation. 18 * 19 * @param dict An object to encode in JSON. 20 * @return string Pretty-printed object representation. 21 */ 22 public function encodeFormatted(array $object) { 23 return $this->encodeFormattedObject($object, 0)."\n"; 24 } 25 26 27 /** 28 * Encode a list in JSON and pretty-print it, discarding keys. 29 * 30 * @param list<wild> List to encode in JSON. 31 * @return string Pretty-printed list representation. 32 */ 33 public function encodeAsList(array $list) { 34 return $this->encodeFormattedArray($list, 0)."\n"; 35 } 36 37 38/* -( Internals )---------------------------------------------------------- */ 39 40 41 /** 42 * Pretty-print a JSON object. 43 * 44 * @param dict Object to format. 45 * @param int Current depth, for indentation. 46 * @return string Pretty-printed value. 47 * @task internal 48 */ 49 private function encodeFormattedObject($object, $depth) { 50 if (empty($object)) { 51 return '{}'; 52 } 53 54 $pre = $this->getIndent($depth); 55 $key_pre = $this->getIndent($depth + 1); 56 $keys = array(); 57 $vals = array(); 58 $max = 0; 59 foreach ($object as $key => $val) { 60 $ekey = $this->encodeFormattedValue((string)$key, 0); 61 $max = max($max, strlen($ekey)); 62 $keys[] = $ekey; 63 $vals[] = $this->encodeFormattedValue($val, $depth + 1); 64 } 65 $key_lines = array(); 66 foreach ($keys as $k => $key) { 67 $key_lines[] = $key_pre.$key.': '.$vals[$k]; 68 } 69 $key_lines = implode(",\n", $key_lines); 70 71 $out = "{\n"; 72 $out .= $key_lines; 73 $out .= "\n"; 74 $out .= $pre.'}'; 75 76 return $out; 77 } 78 79 80 /** 81 * Pretty-print a JSON list. 82 * 83 * @param list List to format. 84 * @param int Current depth, for indentation. 85 * @return string Pretty-printed value. 86 * @task internal 87 */ 88 private function encodeFormattedArray($array, $depth) { 89 if (empty($array)) { 90 return '[]'; 91 } 92 93 $pre = $this->getIndent($depth); 94 $val_pre = $this->getIndent($depth + 1); 95 96 $vals = array(); 97 foreach ($array as $val) { 98 $vals[] = $val_pre.$this->encodeFormattedValue($val, $depth + 1); 99 } 100 $val_lines = implode(",\n", $vals); 101 102 $out = "[\n"; 103 $out .= $val_lines; 104 $out .= "\n"; 105 $out .= $pre.']'; 106 107 return $out; 108 } 109 110 111 /** 112 * Pretty-print a JSON value. 113 * 114 * @param dict Value to format. 115 * @param int Current depth, for indentation. 116 * @return string Pretty-printed value. 117 * @task internal 118 */ 119 private function encodeFormattedValue($value, $depth) { 120 if (is_array($value)) { 121 if (phutil_is_natural_list($value)) { 122 return $this->encodeFormattedArray($value, $depth); 123 } else { 124 return $this->encodeFormattedObject($value, $depth); 125 } 126 } else { 127 if (defined('JSON_UNESCAPED_SLASHES')) { 128 // If we have a new enough version of PHP, disable escaping of slashes 129 // when pretty-printing values. Escaping slashes can defuse an attack 130 // where the attacker embeds "</script>" inside a JSON string, but that 131 // isn't relevant when rendering JSON for human viewers. 132 return json_encode($value, JSON_UNESCAPED_SLASHES); 133 } else { 134 return json_encode($value); 135 } 136 } 137 } 138 139 140 /** 141 * Render a string corresponding to the current indent depth. 142 * 143 * @param int Current depth. 144 * @return string Indentation. 145 * @task internal 146 */ 147 private function getIndent($depth) { 148 if (!$depth) { 149 return ''; 150 } else { 151 return str_repeat(' ', $depth); 152 } 153 } 154 155} 156