blob: 9780200836a060e892e8ef7644df49c4dda5e5b2 [file] [log] [blame]
// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
part of dart.convert;
/**
* Error thrown by JSON serialization if an object cannot be serialized.
*
* The [unsupportedObject] field holds that object that failed to be serialized.
*
* If an object isn't directly serializable, the serializer calls the 'toJson'
* method on the object. If that call fails, the error will be stored in the
* [cause] field. If the call returns an object that isn't directly
* serializable, the [cause] is be null.
*/
class JsonUnsupportedObjectError extends Error {
/** The object that could not be serialized. */
final unsupportedObject;
/** The exception thrown by object's [:toJson:] method, if any. */
final cause;
JsonUnsupportedObjectError(this.unsupportedObject, { this.cause });
String toString() {
if (cause != null) {
return "Calling toJson method on object failed.";
} else {
return "Object toJson method returns non-serializable value.";
}
}
}
/**
* Reports that an object could not be stringified due to cyclic references.
*
* An object that references itself cannot be serialized by [stringify].
* When the cycle is detected, a [JsonCyclicError] is thrown.
*/
class JsonCyclicError extends JsonUnsupportedObjectError {
/** The first object that was detected as part of a cycle. */
JsonCyclicError(Object object): super(object);
String toString() => "Cyclic error in JSON stringify";
}
/**
* An instance of the default implementation of the [JsonCodec].
*
* This instance provides a convenient access to the most common JSON
* use cases.
*
* Examples:
*
* var encoded = JSON.encode([1, 2, { "a": null }]);
* var decoded = JSON.decode('["foo", { "bar": 499 }]');
*/
const JSON = const JsonCodec();
/**
* A [JsonCodec] encodes JSON objects to strings and decodes strings to
* JSON objects.
*/
class JsonCodec extends Codec<Object, String> {
const JsonCodec();
/**
* Creates a `JsonCodec` with the given reviver.
*
* The [reviver] function is called once for each object or list property
* that has been parsed during decoding. The `key` argument is either the
* integer list index for a list property, the map string for object
* properties, or `null` for the final result.
*/
factory JsonCodec.withReviver(reviver(var key, var value)) =
_ReviverJsonCodec;
/**
* Parses the string and returns the resulting Json object.
*
* The optional [reviver] function is called once for each object or list
* property that has been parsed during decoding. The `key` argument is either
* the integer list index for a list property, the map string for object
* properties, or `null` for the final result.
*
* The default [reviver] (when not provided) is the identity function.
*/
Object decode(String str, {reviver(var key, var value)}) {
return new JsonDecoder(reviver).convert(str);
}
/**
* Converts [value] to a JSON string.
*
* If value contains objects that are not directly encodable to a JSON
* string (a value that is not a number, boolean, string, null, list or a map
* with string keys), the [toEncodable] function is used to convert it to an
* object that must be directly encodable.
*
* If [toEncodable] is omitted, it defaults to calling `.toJson()` on the
* unencodable object.
*/
Object encode(Object value, {toEncodable(var object)}) {
return new JsonEncoder(toEncodable).convert(value);
}
JsonEncoder get encoder => new JsonEncoder();
JsonDecoder get decoder => new JsonDecoder(null);
}
typedef _Reviver(var key, var value);
class _ReviverJsonCodec extends JsonCodec {
final _Reviver _reviver;
_ReviverJsonCodec(this._reviver);
Object decode(String str, {reviver(var key, var value)}) {
if (reviver == null) reviver = _reviver;
return new JsonDecoder(reviver).convert(str);
}
JsonDecoder get decoder => new JsonDecoder(_reviver);
}
/**
* This class converts JSON objects to strings.
*/
class JsonEncoder extends Converter<Object, String> {
final _toEncodableFunction;
/**
* Creates a JSON encoder.
*
* The JSON encoder handles numbers, strings, booleans, null, lists and
* maps directly.
*
* Any other object is attempted converted by [toEncodable] to an
* object that is of one of the convertible types.
*
* If [toEncodable] is omitted, it defaults to calling `.toJson()` on
* the object.
*/
JsonEncoder([Object toEncodable(Object nonSerializable)])
: this._toEncodableFunction = toEncodable;
/**
* Converts the given object [o] to its JSON representation.
*
* Directly serializable values are [num], [String], [bool], and [Null], as
* well as some [List] and [Map] values.
* For [List], the elements must all be serializable.
* For [Map], the keys must be [String] and the values must be serializable.
*
* If a value is any other type is attempted serialized, the conversion
* function provided in the constructor is invoked with the object as argument
* and the result, which must be a directly serializable value,
* is serialized instead of the original value.
*
* If the conversion throws, or returns a value that is not directly
* serializable, a [JsonUnsupportedObjectError] exception is thrown.
* If the call throws, the error is caught and stored in the
* [JsonUnsupportedObjectError]'s [:cause:] field.
*
* If a [List] or [Map] contains a reference to itself, directly or through
* other lists or maps, it cannot be serialized and a [JsonCyclicError] is
* thrown.
*
* Json Objects should not change during serialization.
* If an object is serialized more than once, [stringify] is allowed to cache
* the JSON text for it. I.e., if an object changes after it is first
* serialized, the new values may or may not be reflected in the result.
*/
String convert(Object o) => OLD_JSON_LIB.stringify(o, _toEncodableFunction);
/**
* Starts a chunked conversion.
*
* The converter works more efficiently if the given [sink] is a
* [StringConversionSink].
*
* Returns a chunked-conversion sink that accepts at most one object. It is
* an error to invoke `add` more than once on the returned sink.
*/
ChunkedConversionSink<Object> startChunkedConversion(
ChunkedConversionSink<String> sink) {
if (sink is! StringConversionSink) {
sink = new StringConversionSink.from(sink);
}
return new _JsonEncoderSink(sink, _toEncodableFunction);
}
// Override the base-classes bind, to provide a better type.
Stream<String> bind(Stream<Object> stream) => super.bind(stream);
}
/**
* Implements the chunked conversion from object to its JSON representation.
*
* The sink only accepts one value, but will produce output in a chunked way.
*/
class _JsonEncoderSink extends ChunkedConversionSink<Object> {
final Function _toEncodableFunction;
final StringConversionSink _sink;
bool _isDone = false;
_JsonEncoderSink(this._sink, this._toEncodableFunction);
/**
* Encodes the given object [o].
*
* It is an error to invoke this method more than once on any instance. While
* this makes the input effectly non-chunked the output will be generated in
* a chunked way.
*/
void add(Object o) {
if (_isDone) {
throw new StateError("Only one call to add allowed");
}
_isDone = true;
ClosableStringSink stringSink = _sink.asStringSink();
OLD_JSON_LIB.printOn(o, stringSink, _toEncodableFunction);
stringSink.close();
}
void close() { /* do nothing */ }
}
/**
* This class parses JSON strings and builds the corresponding objects.
*/
class JsonDecoder extends Converter<String, Object> {
final _Reviver _reviver;
/**
* Constructs a new JsonDecoder.
*
* The [reviver] may be `null`.
*/
JsonDecoder(reviver(var key, var value)) : this._reviver = reviver;
/**
* Converts the given JSON-string [input] to its corresponding object.
*
* Parsed JSON values are of the types [num], [String], [bool], [Null],
* [List]s of parsed JSON values or [Map]s from [String] to parsed
* JSON values.
*
* If `this` was initialized with a reviver, then the parsing operation
* invokes the reviver on every object or list property that has been parsed.
* The arguments are the property name ([String]) or list index ([int]), and
* the value is the parsed value. The return value of the reviver is used as
* the value of that property instead the parsed value.
*
* Throws [FormatException] if the input is not valid JSON text.
*/
Object convert(String input) => _parseJson(input, _reviver);
/**
* Starts a conversion from a chunked JSON string to its corresponding
* object.
*
* The output [sink] receives exactly one decoded element through `add`.
*/
StringConversionSink startChunkedConversion(
ChunkedConversionSink<Object> sink) {
return new _JsonDecoderSink(_reviver, sink);
}
// Override the base-classes bind, to provide a better type.
Stream<Object> bind(Stream<String> stream) => super.bind(stream);
}
/**
* Implements the chunked conversion from a JSON string to its corresponding
* object.
*
* The sink only creates one object, but its input can be chunked.
*/
// TODO(floitsch): don't accumulate everything before starting to decode.
class _JsonDecoderSink extends _StringSinkConversionSink {
final _Reviver _reviver;
final ChunkedConversionSink<Object> _chunkedSink;
_JsonDecoderSink(this._reviver, this._chunkedSink)
: super(new StringBuffer());
void close() {
super.close();
StringBuffer buffer = _stringSink;
String accumulated = buffer.toString();
buffer.clear();
Object decoded = _parseJson(accumulated, _reviver);
_chunkedSink.add(decoded);
_chunkedSink.close();
}
}
// Internal optimized JSON parsing implementation.
external _parseJson(String source, reviver(key, value));