Overview

Namespaces

  • Webmozart
    • Json

Classes

  • Webmozart\Json\JsonDecoder
  • Webmozart\Json\JsonEncoder
  • Webmozart\Json\JsonError
  • Webmozart\Json\JsonValidator

Exceptions

  • Webmozart\Json\DecodingFailedException
  • Webmozart\Json\EncodingFailedException
  • Webmozart\Json\FileNotFoundException
  • Webmozart\Json\InvalidSchemaException
  • Webmozart\Json\ValidationFailedException
  • Overview
  • Namespace
  • Class
  1:   2:   3:   4:   5:   6:   7:   8:   9:  10:  11:  12:  13:  14:  15:  16:  17:  18:  19:  20:  21:  22:  23:  24:  25:  26:  27:  28:  29:  30:  31:  32:  33:  34:  35:  36:  37:  38:  39:  40:  41:  42:  43:  44:  45:  46:  47:  48:  49:  50:  51:  52:  53:  54:  55:  56:  57:  58:  59:  60:  61:  62:  63:  64:  65:  66:  67:  68:  69:  70:  71:  72:  73:  74:  75:  76:  77:  78:  79:  80:  81:  82:  83:  84:  85:  86:  87:  88:  89:  90:  91:  92:  93:  94:  95:  96:  97:  98:  99: 100: 101: 102: 103: 104: 105: 106: 107: 108: 109: 110: 111: 112: 113: 114: 115: 116: 117: 118: 119: 120: 121: 122: 123: 124: 125: 126: 127: 128: 129: 130: 131: 132: 133: 134: 135: 136: 137: 138: 139: 140: 141: 142: 143: 144: 145: 146: 147: 148: 149: 150: 151: 152: 153: 154: 155: 156: 157: 158: 159: 160: 161: 162: 163: 164: 165: 166: 167: 168: 169: 170: 171: 172: 173: 174: 175: 176: 177: 178: 179: 180: 181: 182: 183: 184: 185: 186: 187: 188: 189: 190: 191: 192: 193: 194: 195: 196: 197: 198: 199: 200: 201: 202: 203: 204: 205: 206: 207: 208: 209: 210: 211: 212: 213: 214: 215: 216: 217: 218: 219: 220: 221: 222: 223: 224: 225: 226: 227: 228: 229: 230: 231: 232: 233: 234: 235: 236: 237: 238: 239: 240: 241: 242: 243: 244: 245: 246: 247: 248: 249: 250: 251: 252: 253: 254: 255: 256: 257: 258: 259: 260: 261: 262: 263: 264: 265: 266: 267: 268: 269: 270: 271: 272: 273: 274: 275: 276: 277: 278: 279: 280: 281: 282: 283: 284: 285: 286: 287: 288: 289: 290: 291: 292: 293: 294: 295: 296: 297: 298: 299: 300: 301: 302: 303: 304: 305: 306: 307: 308: 309: 310: 311: 312: 313: 314: 315: 316: 317: 318: 319: 320: 321: 322: 323: 324: 325: 326: 327: 328: 329: 330: 331: 332: 333: 334: 335: 336: 337: 338: 339: 340: 341: 342: 343: 344: 345: 346: 347: 348: 349: 350: 351: 352: 353: 354: 355: 356: 357: 358: 359: 360: 361: 362: 363: 364: 365: 366: 367: 368: 369: 370: 371: 372: 373: 374: 375: 376: 377: 378: 379: 380: 381: 382: 383: 384: 385: 386: 387: 388: 389: 390: 391: 392: 393: 394: 395: 396: 397: 398: 399: 400: 401: 402: 403: 404: 405: 406: 407: 408: 409: 410: 411: 412: 413: 414: 415: 416: 417: 418: 419: 420: 421: 422: 423: 424: 425: 426: 427: 428: 429: 430: 431: 432: 433: 434: 435: 436: 437: 438: 439: 440: 441: 442: 443: 444: 445: 446: 447: 448: 449: 450: 451: 452: 453: 454: 455: 456: 457: 458: 459: 460: 461: 462: 463: 464: 465: 466: 467: 468: 469: 470: 471: 472: 473: 474: 475: 476: 477: 478: 479: 480: 481: 482: 483: 484: 485: 486: 487: 488: 489: 490: 491: 492: 493: 494: 495: 496: 497: 498: 499: 500: 501: 502: 503: 504: 505: 506: 507: 508: 509: 510: 511: 512: 513: 514: 515: 516: 517: 518: 519: 520: 521: 522: 523: 524: 525: 526: 527: 528: 529: 530: 531: 532: 533: 534: 535: 536: 537: 538: 539: 540: 541: 542: 543: 544: 545: 546: 547: 548: 549: 550: 551: 552: 553: 554: 555: 556: 557: 558: 559: 560: 561: 562: 563: 564: 565: 566: 567: 568: 569: 570: 571: 572: 573: 574: 575: 576: 577: 578: 579: 580: 581: 582: 583: 584: 585: 586: 587: 588: 589: 590: 591: 
<?php

/*
 * This file is part of the Webmozart JSON package.
 *
 * (c) Bernhard Schussek <bschussek@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Webmozart\Json;

/**
 * Encodes data as JSON.
 *
 * @since  1.0
 *
 * @author Bernhard Schussek <bschussek@gmail.com>
 */
class JsonEncoder
{
    /**
     * Encode a value as JSON array.
     */
    const JSON_ARRAY = 1;

    /**
     * Encode a value as JSON object.
     */
    const JSON_OBJECT = 2;

    /**
     * Encode a value as JSON string.
     */
    const JSON_STRING = 3;

    /**
     * Encode a value as JSON integer or float.
     */
    const JSON_NUMBER = 4;

    /**
     * @var JsonValidator
     */
    private $validator;

    /**
     * @var int
     */
    private $arrayEncoding = self::JSON_ARRAY;

    /**
     * @var int
     */
    private $numericEncoding = self::JSON_STRING;

    /**
     * @var bool
     */
    private $gtLtEscaped = false;

    /**
     * @var bool
     */
    private $ampersandEscaped = false;

    /**
     * @var bool
     */
    private $singleQuoteEscaped = false;

    /**
     * @var bool
     */
    private $doubleQuoteEscaped = false;

    /**
     * @var bool
     */
    private $slashEscaped = true;

    /**
     * @var bool
     */
    private $unicodeEscaped = true;

    /**
     * @var bool
     */
    private $prettyPrinting = false;

    /**
     * @var bool
     */
    private $terminatedWithLineFeed = false;

    /**
     * @var int
     */
    private $maxDepth = 512;

    /**
     * Creates a new encoder.
     */
    public function __construct()
    {
        $this->validator = new JsonValidator();
    }

    /**
     * Encodes data as JSON.
     *
     * If a schema is passed, the value is validated against that schema before
     * encoding. The schema may be passed as file path or as object returned
     * from `JsonDecoder::decodeFile($schemaFile)`.
     *
     * You can adjust the decoding with the various setters in this class.
     *
     * @param mixed         $data   The data to encode.
     * @param string|object $schema The schema file or object.
     *
     * @return string The JSON string.
     *
     * @throws EncodingFailedException   If the data could not be encoded.
     * @throws ValidationFailedException If the data fails schema validation.
     * @throws InvalidSchemaException    If the schema is invalid.
     */
    public function encode($data, $schema = null)
    {
        if (null !== $schema) {
            $errors = $this->validator->validate($data, $schema);

            if (count($errors) > 0) {
                throw ValidationFailedException::fromErrors($errors);
            }
        }

        $options = 0;

        if (self::JSON_OBJECT === $this->arrayEncoding) {
            $options |= JSON_FORCE_OBJECT;
        }

        if (self::JSON_NUMBER === $this->numericEncoding) {
            $options |= JSON_NUMERIC_CHECK;
        }

        if ($this->gtLtEscaped) {
            $options |= JSON_HEX_TAG;
        }

        if ($this->ampersandEscaped) {
            $options |= JSON_HEX_AMP;
        }

        if ($this->singleQuoteEscaped) {
            $options |= JSON_HEX_APOS;
        }

        if ($this->doubleQuoteEscaped) {
            $options |= JSON_HEX_QUOT;
        }

        if (PHP_VERSION_ID >= 50400) {
            if (!$this->slashEscaped) {
                $options |= JSON_UNESCAPED_SLASHES;
            }

            if (!$this->unicodeEscaped) {
                $options |= JSON_UNESCAPED_UNICODE;
            }

            if ($this->prettyPrinting) {
                $options |= JSON_PRETTY_PRINT;
            }
        }

        if (PHP_VERSION_ID >= 50500) {
            $maxDepth = $this->maxDepth;

            // We subtract 1 from the max depth to make JsonDecoder and
            // JsonEncoder consistent. json_encode() and json_decode() behave
            // differently for their depth values. See the test cases for
            // examples.
            // HHVM does not have this inconsistency.
            if (!defined('HHVM_VERSION')) {
                --$maxDepth;
            }

            $encoded = json_encode($data, $options, $maxDepth);
        } else {
            $encoded = json_encode($data, $options);
        }

        if (false === $encoded) {
            throw new EncodingFailedException(sprintf(
                'The data could not be encoded as JSON: %s',
                JsonError::getLastErrorMessage()
            ), json_last_error());
        }

        if ($this->terminatedWithLineFeed) {
            $encoded .= "\n";
        }

        return $encoded;
    }

    /**
     * Encodes data into a JSON file.
     *
     * @param mixed         $data   The data to encode.
     * @param string        $file   The path where the JSON file will be stored.
     * @param string|object $schema The schema file or object.
     *
     * @throws EncodingFailedException   If the data could not be encoded.
     * @throws ValidationFailedException If the data fails schema validation.
     * @throws InvalidSchemaException    If the schema is invalid.
     *
     * @see encode
     */
    public function encodeFile($data, $file, $schema = null)
    {
        try {
            // Right now, it's sufficient to just write the file. In the future,
            // this will diff existing files with the given data and only do
            // in-place modifications where necessary.
            file_put_contents($file, $this->encode($data, $schema));
        } catch (EncodingFailedException $e) {
            // Add the file name to the exception
            throw new EncodingFailedException(sprintf(
                'An error happened while encoding %s: %s',
                $file,
                $e->getMessage()
            ), $e->getCode(), $e);
        } catch (ValidationFailedException $e) {
            // Add the file name to the exception
            throw new ValidationFailedException(sprintf(
                "Validation failed while encoding %s:\n%s",
                $file,
                $e->getErrorsAsString()
            ), $e->getErrors(), $e->getCode(), $e);
        } catch (InvalidSchemaException $e) {
            // Add the file name to the exception
            throw new InvalidSchemaException(sprintf(
                'An error happened while encoding %s: %s',
                $file,
                $e->getMessage()
            ), $e->getCode(), $e);
        }
    }

    /**
     * Returns the encoding of non-associative arrays.
     *
     * @return int One of the constants {@link JSON_OBJECT} and {@link JSON_ARRAY}.
     */
    public function getArrayEncoding()
    {
        return $this->arrayEncoding;
    }

    /**
     * Sets the encoding of non-associative arrays.
     *
     * By default, non-associative arrays are decoded as JSON arrays.
     *
     * @param int $encoding One of the constants {@link JSON_OBJECT} and {@link JSON_ARRAY}.
     *
     * @throws \InvalidArgumentException If the passed encoding is invalid.
     */
    public function setArrayEncoding($encoding)
    {
        if (self::JSON_ARRAY !== $encoding && self::JSON_OBJECT !== $encoding) {
            throw new \InvalidArgumentException(sprintf(
                'Expected JsonEncoder::JSON_ARRAY or JsonEncoder::JSON_OBJECT. '.
                'Got: %s',
                $encoding
            ));
        }

        $this->arrayEncoding = $encoding;
    }

    /**
     * Returns the encoding of numeric strings.
     *
     * @return int One of the constants {@link JSON_STRING} and {@link JSON_NUMBER}.
     */
    public function getNumericEncoding()
    {
        return $this->numericEncoding;
    }

    /**
     * Sets the encoding of numeric strings.
     *
     * By default, non-associative arrays are decoded as JSON strings.
     *
     * @param int $encoding One of the constants {@link JSON_STRING} and {@link JSON_NUMBER}.
     *
     * @throws \InvalidArgumentException If the passed encoding is invalid.
     */
    public function setNumericEncoding($encoding)
    {
        if (self::JSON_NUMBER !== $encoding && self::JSON_STRING !== $encoding) {
            throw new \InvalidArgumentException(sprintf(
                'Expected JsonEncoder::JSON_NUMBER or JsonEncoder::JSON_STRING. '.
                'Got: %s',
                $encoding
            ));
        }

        $this->numericEncoding = $encoding;
    }

    /**
     * Returns whether ampersands (&) are escaped.
     *
     * If `true`, ampersands will be escaped as "\u0026".
     *
     * By default, ampersands are not escaped.
     *
     * @return bool Whether ampersands are escaped.
     */
    public function isAmpersandEscaped()
    {
        return $this->ampersandEscaped;
    }

    /**
     * Sets whether ampersands (&) should be escaped.
     *
     * If `true`, ampersands will be escaped as "\u0026".
     *
     * By default, ampersands are not escaped.
     *
     * @param bool $enabled Whether ampersands should be escaped.
     */
    public function setEscapeAmpersand($enabled)
    {
        $this->ampersandEscaped = $enabled;
    }

    /**
     * Returns whether double quotes (") are escaped.
     *
     * If `true`, double quotes will be escaped as "\u0022".
     *
     * By default, double quotes are not escaped.
     *
     * @return bool Whether double quotes are escaped.
     */
    public function isDoubleQuoteEscaped()
    {
        return $this->doubleQuoteEscaped;
    }

    /**
     * Sets whether double quotes (") should be escaped.
     *
     * If `true`, double quotes will be escaped as "\u0022".
     *
     * By default, double quotes are not escaped.
     *
     * @param bool $enabled Whether double quotes should be escaped.
     */
    public function setEscapeDoubleQuote($enabled)
    {
        $this->doubleQuoteEscaped = $enabled;
    }

    /**
     * Returns whether single quotes (') are escaped.
     *
     * If `true`, single quotes will be escaped as "\u0027".
     *
     * By default, single quotes are not escaped.
     *
     * @return bool Whether single quotes are escaped.
     */
    public function isSingleQuoteEscaped()
    {
        return $this->singleQuoteEscaped;
    }

    /**
     * Sets whether single quotes (") should be escaped.
     *
     * If `true`, single quotes will be escaped as "\u0027".
     *
     * By default, single quotes are not escaped.
     *
     * @param bool $enabled Whether single quotes should be escaped.
     */
    public function setEscapeSingleQuote($enabled)
    {
        $this->singleQuoteEscaped = $enabled;
    }

    /**
     * Returns whether forward slashes (/) are escaped.
     *
     * If `true`, forward slashes will be escaped as "\/".
     *
     * By default, forward slashes are not escaped.
     *
     * @return bool Whether forward slashes are escaped.
     */
    public function isSlashEscaped()
    {
        return $this->slashEscaped;
    }

    /**
     * Sets whether forward slashes (") should be escaped.
     *
     * If `true`, forward slashes will be escaped as "\/".
     *
     * By default, forward slashes are not escaped.
     *
     * @param bool $enabled Whether forward slashes should be escaped.
     */
    public function setEscapeSlash($enabled)
    {
        $this->slashEscaped = $enabled;
    }

    /**
     * Returns whether greater than/less than symbols (>, <) are escaped.
     *
     * If `true`, greater than will be escaped as "\u003E" and less than as
     * "\u003C".
     *
     * By default, greater than/less than symbols are not escaped.
     *
     * @return bool Whether greater than/less than symbols are escaped.
     */
    public function isGtLtEscaped()
    {
        return $this->gtLtEscaped;
    }

    /**
     * Sets whether greater than/less than symbols (>, <) should be escaped.
     *
     * If `true`, greater than will be escaped as "\u003E" and less than as
     * "\u003C".
     *
     * By default, greater than/less than symbols are not escaped.
     *
     * @param bool $enabled Whether greater than/less than should be escaped.
     */
    public function setEscapeGtLt($enabled)
    {
        $this->gtLtEscaped = $enabled;
    }

    /**
     * Returns whether unicode characters are escaped.
     *
     * If `true`, unicode characters will be escaped as hexadecimals strings.
     * For example, "ü" will be escaped as "\u00fc".
     *
     * By default, unicode characters are escaped.
     *
     * @return bool Whether unicode characters are escaped.
     */
    public function isUnicodeEscaped()
    {
        return $this->unicodeEscaped;
    }

    /**
     * Sets whether unicode characters should be escaped.
     *
     * If `true`, unicode characters will be escaped as hexadecimals strings.
     * For example, "ü" will be escaped as "\u00fc".
     *
     * By default, unicode characters are escaped.
     *
     * @param bool $enabled Whether unicode characters should be escaped.
     */
    public function setEscapeUnicode($enabled)
    {
        $this->unicodeEscaped = $enabled;
    }

    /**
     * Returns whether JSON strings are formatted for better readability.
     *
     * If `true`, line breaks will be added after object properties and array
     * entries. Each new nesting level will be indented by four spaces.
     *
     * By default, pretty printing is not enabled.
     *
     * @return bool Whether JSON strings are formatted.
     */
    public function isPrettyPrinting()
    {
        return $this->prettyPrinting;
    }

    /**
     * Sets whether JSON strings should be formatted for better readability.
     *
     * If `true`, line breaks will be added after object properties and array
     * entries. Each new nesting level will be indented by four spaces.
     *
     * By default, pretty printing is not enabled.
     *
     * @param bool $prettyPrinting Whether JSON strings should be formatted.
     */
    public function setPrettyPrinting($prettyPrinting)
    {
        $this->prettyPrinting = $prettyPrinting;
    }

    /**
     * Returns whether JSON strings are terminated with a line feed.
     *
     * By default, JSON strings are not terminated with a line feed.
     *
     * @return bool Whether JSON strings are terminated with a line feed.
     */
    public function isTerminatedWithLineFeed()
    {
        return $this->terminatedWithLineFeed;
    }

    /**
     * Sets whether JSON strings should be terminated with a line feed.
     *
     * By default, JSON strings are not terminated with a line feed.
     *
     * @param bool $enabled Whether JSON strings should be terminated with a
     *                      line feed.
     */
    public function setTerminateWithLineFeed($enabled)
    {
        $this->terminatedWithLineFeed = $enabled;
    }

    /**
     * Returns the maximum recursion depth.
     *
     * A depth of zero means that objects are not allowed. A depth of one means
     * only one level of objects or arrays is allowed.
     *
     * @return int The maximum recursion depth.
     */
    public function getMaxDepth()
    {
        return $this->maxDepth;
    }

    /**
     * Sets the maximum recursion depth.
     *
     * If the depth is exceeded during encoding, an {@link EncodingFailedException}
     * will be thrown.
     *
     * A depth of zero means that objects are not allowed. A depth of one means
     * only one level of objects or arrays is allowed.
     *
     * @param int $maxDepth The maximum recursion depth.
     *
     * @throws \InvalidArgumentException If the depth is not an integer greater
     *                                   than or equal to zero.
     */
    public function setMaxDepth($maxDepth)
    {
        if (!is_int($maxDepth)) {
            throw new \InvalidArgumentException(sprintf(
                'The maximum depth should be an integer. Got: %s',
                is_object($maxDepth) ? get_class($maxDepth) : gettype($maxDepth)
            ));
        }

        if ($maxDepth < 1) {
            throw new \InvalidArgumentException(sprintf(
                'The maximum depth should 1 or greater. Got: %s',
                $maxDepth
            ));
        }

        $this->maxDepth = $maxDepth;
    }
}
Webmozart JSON API API documentation generated by ApiGen