1
<?php
2

3
declare(strict_types=1);
4

5
/**
6
 * Copyright (c) 2018-2020 Andreas Möller
7
 *
8
 * For the full copyright and license information, please view
9
 * the LICENSE.md file that was distributed with this source code.
10
 *
11
 * @see https://github.com/ergebnis/json-printer
12
 */
13

14
namespace Ergebnis\Json\Printer;
15

16
final class Printer implements PrinterInterface
17
{
18
    /**
19
     * This code is adopted from composer/composer (originally licensed under MIT by Nils Adermann <naderman@naderman.de>
20
     * and Jordi Boggiano <j.boggiano@seld.be>), who adopted it from a blog post by Dave Perrett (originally licensed
21
     * under MIT by Dave Perrett <mail@recursive-design.com>).
22
     *
23
     * The primary objective of the adoption is
24
     *
25
     * - turn static method into an instance method
26
     * - allow to specify indent
27
     * - allow to specify new-line character sequence
28
     *
29
     * If you observe closely, the options for un-escaping unicode characters and slashes have been removed. Since this
30
     * package requires PHP 7, there is no need to implement this in user-land code.
31
     *
32
     * @see https://github.com/composer/composer/blob/1.6.0/src/Composer/Json/JsonFormatter.php#L25-L126
33
     * @see https://www.daveperrett.com/articles/2008/03/11/format-json-with-php/
34
     * @see http://php.net/manual/en/function.json-encode.php
35
     * @see http://php.net/manual/en/json.constants.php
36
     *
37
     * @param string $json
38
     * @param string $indent
39
     * @param string $newLine
40
     *
41
     * @throws \InvalidArgumentException
42
     *
43
     * @return string
44
     */
45 1
    public function print(string $json, string $indent = '    ', string $newLine = \PHP_EOL): string
46
    {
47 1
        if (null === \json_decode($json) && \JSON_ERROR_NONE !== \json_last_error()) {
48 1
            throw new \InvalidArgumentException(\sprintf(
49 1
                '"%s" is not valid JSON.',
50 1
                $json
51
            ));
52
        }
53

54 1
        if (1 !== \preg_match('/^( +|\t+)$/', $indent)) {
55 1
            throw new \InvalidArgumentException(\sprintf(
56 1
                '"%s" is not a valid indent.',
57 1
                $indent
58
            ));
59
        }
60

61 1
        if (1 !== \preg_match('/^(?>\r\n|\n|\r)$/', $newLine)) {
62 1
            throw new \InvalidArgumentException(\sprintf(
63 1
                '"%s" is not a valid new-line character sequence.',
64 1
                $newLine
65
            ));
66
        }
67

68 1
        $printed = '';
69 1
        $indentLevel = 0;
70 1
        $length = \mb_strlen($json);
71 1
        $withinStringLiteral = false;
72 1
        $stringLiteral = '';
73 1
        $noEscape = true;
74

75 1
        for ($i = 0; $i < $length; ++$i) {
76
            /**
77
             * Grab the next character in the string.
78
             */
79 1
            $character = \mb_substr($json, $i, 1);
80

81
            /**
82
             * Are we inside a quoted string literal?
83
             */
84 1
            if ('"' === $character && $noEscape) {
85 1
                $withinStringLiteral = !$withinStringLiteral;
86
            }
87

88
            /**
89
             * Collect characters if we are inside a quoted string literal.
90
             */
91 1
            if ($withinStringLiteral) {
92 1
                $stringLiteral .= $character;
93 1
                $noEscape = '\\' === $character ? !$noEscape : true;
94

95 1
                continue;
96
            }
97

98
            /**
99
             * Process string literal if we are about to leave it.
100
             */
101 1
            if ('' !== $stringLiteral) {
102 1
                $printed .= $stringLiteral . $character;
103 1
                $stringLiteral = '';
104

105 1
                continue;
106
            }
107

108
            /**
109
             * Ignore whitespace outside of string literal.
110
             */
111 1
            if ('' === \trim($character)) {
112 1
                continue;
113
            }
114

115
            /**
116
             * Ensure space after ":" character.
117
             */
118 1
            if (':' === $character) {
119 1
                $printed .= ': ';
120

121 1
                continue;
122
            }
123

124
            /**
125
             * Output a new line after "," character and and indent the next line.
126
             */
127 1
            if (',' === $character) {
128 1
                $printed .= $character . $newLine . \str_repeat($indent, $indentLevel);
129

130 1
                continue;
131
            }
132

133
            /**
134
             * Output a new line after "{" and "[" and indent the next line.
135
             */
136 1
            if ('{' === $character || '[' === $character) {
137 1
                ++$indentLevel;
138

139 1
                $printed .= $character . $newLine . \str_repeat($indent, $indentLevel);
140

141 1
                continue;
142
            }
143

144
            /**
145
             * Output a new line after "}" and "]" and indent the next line.
146
             */
147 1
            if ('}' === $character || ']' === $character) {
148 1
                --$indentLevel;
149

150 1
                $trimmed = \rtrim($printed);
151 1
                $previousNonWhitespaceCharacter = \mb_substr($trimmed, -1);
152

153
                /**
154
                 * Collapse empty {} and [].
155
                 */
156 1
                if ('{' === $previousNonWhitespaceCharacter || '[' === $previousNonWhitespaceCharacter) {
157 1
                    $printed = $trimmed . $character;
158

159 1
                    continue;
160
                }
161

162 1
                $printed .= $newLine . \str_repeat($indent, $indentLevel);
163
            }
164

165 1
            $printed .= $character;
166
        }
167

168 1
        return $printed;
169
    }
170
}

Read our documentation on viewing source code .

Loading