1: <?php
2: /**
3: * Json helper functions.
4: *
5: * Adds the following functionality:
6: * <ul>
7: * <li>JSON string validation: CBJson::isValid()</li>
8: * <li>JSON formatting/indentation: CBJson::indent()</li>
9: * <li>JSON soft decoding: CBJson::softDecode*() (<i>does not fail if json is invalid</i>)</li>
10: * </ul>
11: *
12: * @since 1.0
13: * @package Components
14: * @author Konstantinos Filios <konfilios@gmail.com>
15: */
16: class CBJson
17: {
18:
19: /**
20: * Check if passed string is valid json.
21: *
22: * @param string $json Json string.
23: *
24: * @return boolean True if passed $json is valid
25: */
26: static public function isValid($json)
27: {
28: return is_string($json) && (json_decode($json) != null);
29: }
30:
31: /**
32: * Indent passed $json string to increase human-readability.
33: *
34: * @see http://recursive-design.com/blog/2008/03/11/format-json-with-php/
35: *
36: * @param string $json Unformatted json.
37: * @param string $indentStr Indentation string (tabulator).
38: * @param string $newLine Line terminator.
39: *
40: * @return string
41: */
42: static public function indent($json, $indentStr = ' ', $newLine = "\n")
43: {
44:
45: $result = '';
46: $pos = 0;
47: $strLen = strlen($json);
48: $prevChar = '';
49: $outOfQuotes = true;
50:
51: for ($i = 0; $i <= $strLen; $i++) {
52:
53: // Grab the next character in the string.
54: $char = substr($json, $i, 1);
55:
56: // Are we inside a quoted string?
57: if ($char == '"' && $prevChar != '\\') {
58: $outOfQuotes = !$outOfQuotes;
59:
60: // If this character is the end of an element,
61: // output a new line and indent the next line.
62: } else if (($char == '}' || $char == ']') && $outOfQuotes) {
63: $result .= $newLine;
64: $pos--;
65: for ($j = 0; $j < $pos; $j++) {
66: $result .= $indentStr;
67: }
68: }
69:
70: // Add the character to the result string.
71: $result .= $char;
72:
73: // If the last character was the beginning of an element,
74: // output a new line and indent the next line.
75: if (($char == ',' || $char == '{' || $char == '[') && $outOfQuotes) {
76: $result .= $newLine;
77: if ($char == '{' || $char == '[') {
78: $pos++;
79: }
80:
81: for ($j = 0; $j < $pos; $j++) {
82: $result .= $indentStr;
83: }
84: }
85:
86: $prevChar = $char;
87: }
88:
89: return $result;
90: }
91:
92: /**
93: * Returns string/text describing the last error occured while json encoding/decoding.
94: *
95: * This is supported only for php 5.3 or later.
96: *
97: * @see http://www.php.net/manual/en/function.json-last-error.php
98: *
99: * @return string
100: */
101: static public function getLastErrorString()
102: {
103: if (function_exists('json_last_error')) {
104: $errorCode = json_last_error();
105:
106: switch ($errorCode) {
107: case JSON_ERROR_NONE:
108: return false;
109: case JSON_ERROR_DEPTH:
110: return 'Maximum stack depth exceeded';
111: case JSON_ERROR_STATE_MISMATCH:
112: return 'Invalid or malformed JSON';
113: case JSON_ERROR_CTRL_CHAR:
114: return 'Control character error, possibly incorrectly encoded';
115: case JSON_ERROR_SYNTAX:
116: return 'Syntax error';
117: case JSON_ERROR_UTF8:
118: return 'Malformed UTF-8 characters, possibly incorrectly encoded';
119: default:
120: return 'Unsupported error code '.$errorCode;
121: }
122: } else {
123: return false;
124: // $error = error_get_last();
125: // if (empty($error) || empty($error['message'])) {
126: // return false;
127: // }
128: // return $error['message'];
129: }
130: }
131:
132: /**
133: * Try to json-decode input.
134: *
135: * @param string $jsonInput
136: * @return mixed
137: */
138: static public function softDecode($jsonInput)
139: {
140: try {
141: $jsonOutput = self::decodeAssoc($jsonInput);
142: } catch (Exception $e) {
143: // Could not json decode, retain value
144: $jsonOutput = $jsonInput;
145: }
146:
147: return $jsonOutput;
148: }
149:
150: /**
151: * Try to json-decode array elements.
152: *
153: * If elements are not json-decoded their original values are retained.
154: *
155: * @param array $jsonInputs
156: * @return array
157: */
158: static public function softDecodeArray(array $jsonInputs)
159: {
160: $jsonOutputs = array();
161: foreach ($jsonInputs as $key=>$jsonInput) {
162: $jsonOutputs[$key] = (is_array($jsonInput) || is_object($jsonInput))
163: ? self::softDecodeArray($jsonInput)
164: : self::softDecode($jsonInput);
165: }
166: return $jsonOutputs;
167: }
168:
169: /**
170: * Decode json input.
171: *
172: * @param string $jsonInput
173: * @return mixed
174: * @throws CException
175: */
176: static public function decodeAssoc($jsonInput)
177: {
178: // Object is optional
179: if (empty($jsonInput)) {
180: // Input string is empty, nothing to json-decode
181: return null;
182: }
183:
184: // Decode the object into an array
185: $objectOutput = json_decode($jsonInput, true);
186:
187: if (($objectOutput === null) && ($jsonInput !== 'null')) {
188: // Json_decode failed
189: throw new CException('Could not JSON-decode input object');
190: }
191:
192: return $objectOutput;
193: }
194: }
195: