ergebnis / json-printer
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
     * @throws \InvalidArgumentException
38
     */
39 1
    public function print(string $json, string $indent = '    ', string $newLine = \PHP_EOL): string
40
    {
41 1
        if (null === \json_decode($json) && \JSON_ERROR_NONE !== \json_last_error()) {
42 1
            throw new \InvalidArgumentException(\sprintf(
43
                '"%s" is not valid JSON.',
44 1
                $json
45
            ));
46
        }
47

48 1
        if (1 !== \preg_match('/^( +|\t+)$/', $indent)) {
49 1
            throw new \InvalidArgumentException(\sprintf(
50
                '"%s" is not a valid indent.',
51 1
                $indent
52
            ));
53
        }
54

55 1
        if (1 !== \preg_match('/^(?>\r\n|\n|\r)$/', $newLine)) {
56 1
            throw new \InvalidArgumentException(\sprintf(
57
                '"%s" is not a valid new-line character sequence.',
58 1
                $newLine
59
            ));
60
        }
61

62 1
        $printed = '';
63 1
        $indentLevel = 0;
64 1
        $length = \mb_strlen($json);
65 1
        $withinStringLiteral = false;
66 1
        $stringLiteral = '';
67 1
        $noEscape = true;
68

69 1
        for ($i = 0; $i < $length; ++$i) {
70
            /**
71
             * Grab the next character in the string.
72
             */
73 1
            $character = \mb_substr($json, $i, 1);
74

75
            /**
76
             * Are we inside a quoted string literal?
77
             */
78 1
            if ('"' === $character && $noEscape) {
79 1
                $withinStringLiteral = !$withinStringLiteral;
80
            }
81

82
            /**
83
             * Collect characters if we are inside a quoted string literal.
84
             */
85 1
            if ($withinStringLiteral) {
86 1
                $stringLiteral .= $character;
87 1
                $noEscape = '\\' === $character ? !$noEscape : true;
88

89 1
                continue;
90
            }
91

92
            /**
93
             * Process string literal if we are about to leave it.
94
             */
95 1
            if ('' !== $stringLiteral) {
96 1
                $printed .= $stringLiteral . $character;
97 1
                $stringLiteral = '';
98

99 1
                continue;
100
            }
101

102
            /**
103
             * Ignore whitespace outside of string literal.
104
             */
105 1
            if ('' === \trim($character)) {
106 1
                continue;
107
            }
108

109
            /**
110
             * Ensure space after ":" character.
111
             */
112 1
            if (':' === $character) {
113 1
                $printed .= ': ';
114

115 1
                continue;
116
            }
117

118
            /**
119
             * Output a new line after "," character and and indent the next line.
120
             */
121 1
            if (',' === $character) {
122 1
                $printed .= $character . $newLine . \str_repeat($indent, $indentLevel);
123

124 1
                continue;
125
            }
126

127
            /**
128
             * Output a new line after "{" and "[" and indent the next line.
129
             */
130 1
            if ('{' === $character || '[' === $character) {
131 1
                ++$indentLevel;
132

133 1
                $printed .= $character . $newLine . \str_repeat($indent, $indentLevel);
134

135 1
                continue;
136
            }
137

138
            /**
139
             * Output a new line after "}" and "]" and indent the next line.
140
             */
141 1
            if ('}' === $character || ']' === $character) {
142 1
                --$indentLevel;
143

144 1
                $trimmed = \rtrim($printed);
145 1
                $previousNonWhitespaceCharacter = \mb_substr($trimmed, -1);
146

147
                /**
148
                 * Collapse empty {} and [].
149
                 */
150 1
                if ('{' === $previousNonWhitespaceCharacter || '[' === $previousNonWhitespaceCharacter) {
151 1
                    $printed = $trimmed . $character;
152

153 1
                    continue;
154
                }
155

156 1
                $printed .= $newLine . \str_repeat($indent, $indentLevel);
157
            }
158

159 1
            $printed .= $character;
160
        }
161

162 1
        return $printed;
163
    }
164
}

Read our documentation on viewing source code .

Loading