Commit e17c68c4 authored by yangguo's avatar yangguo Committed by Commit bot

[JSON] implement indentation in the BasicJsonStringifier and expose via API.

R=jochen@chromium.org, verwaest@chromium.org

Review-Url: https://codereview.chromium.org/1922603006
Cr-Commit-Position: refs/heads/master@{#36392}
parent e7ab2960
......@@ -1658,7 +1658,8 @@ class V8_EXPORT JSON {
* \return The corresponding string if successfully stringified.
*/
static V8_WARN_UNUSED_RESULT MaybeLocal<String> Stringify(
Local<Context> context, Local<Object> json_object);
Local<Context> context, Local<Object> json_object,
Local<String> gap = Local<String>());
};
......
......@@ -2769,12 +2769,17 @@ Local<Value> JSON::Parse(Local<String> json_string) {
}
MaybeLocal<String> JSON::Stringify(Local<Context> context,
Local<Object> json_object) {
Local<Object> json_object,
Local<String> gap) {
PREPARE_FOR_EXECUTION(context, JSON, Stringify, String);
i::Handle<i::Object> object = Utils::OpenHandle(*json_object);
i::Handle<i::String> gap_string = gap.IsEmpty()
? isolate->factory()->empty_string()
: Utils::OpenHandle(*gap);
i::Handle<i::Object> maybe;
has_pending_exception =
!i::Runtime::BasicJsonStringify(isolate, object).ToHandle(&maybe);
!i::Runtime::BasicJsonStringify(isolate, object, gap_string)
.ToHandle(&maybe);
RETURN_ON_FAILED_EXECUTION(String);
Local<String> result;
has_pending_exception =
......
......@@ -202,7 +202,7 @@ function JSONSerialize(key, holder, replacer, stack, indent, gap) {
function JSONStringify(value, replacer, space) {
if (arguments.length === 1 && !IS_PROXY(value)) {
return %BasicJSONStringify(value);
return %BasicJSONStringify(value, "");
}
if (!IS_CALLABLE(replacer) && %is_arraylike(replacer)) {
var property_list = new InternalArray();
......@@ -248,8 +248,8 @@ function JSONStringify(value, replacer, space) {
} else {
gap = "";
}
if (!IS_CALLABLE(replacer) && !property_list && !gap && !IS_PROXY(value)) {
return %BasicJSONStringify(value);
if (!IS_CALLABLE(replacer) && !property_list && !IS_PROXY(value)) {
return %BasicJSONStringify(value, gap);
}
return JSONSerialize('', {'': value}, replacer, new Stack(), "", gap);
}
......@@ -285,11 +285,14 @@ utils.InstallFunctions(GlobalDate.prototype, DONT_ENUM, [
// -------------------------------------------------------------------
// JSON Builtins
function JsonSerializeAdapter(key, object) {
function JsonSerializeAdapter(key, object, indent, gap) {
var holder = {};
holder[key] = object;
// No need to pass the actual holder since there is no replacer function.
return JSONSerialize(key, holder, UNDEFINED, new Stack(), "", "");
var current_indent = "";
for (var i = 0; i < indent; i++) current_indent += gap;
return JSONSerialize(
key, holder, UNDEFINED, new Stack(), current_indent, gap);
}
%InstallToContext(["json_serialize_adapter", JsonSerializeAdapter]);
......
......@@ -16,7 +16,9 @@ namespace internal {
class BasicJsonStringifier BASE_EMBEDDED {
public:
explicit BasicJsonStringifier(Isolate* isolate);
BasicJsonStringifier(Isolate* isolate, Handle<String> gap);
~BasicJsonStringifier() { DeleteArray(gap_); }
MUST_USE_RESULT MaybeHandle<Object> Stringify(Handle<Object> object);
......@@ -65,9 +67,10 @@ class BasicJsonStringifier BASE_EMBEDDED {
Result Serialize_(Handle<Object> object, bool comma, Handle<Object> key);
void SerializeDeferredKey(bool deferred_comma, Handle<Object> deferred_key) {
if (deferred_comma) builder_.AppendCharacter(',');
Separator(!deferred_comma);
SerializeString(Handle<String>::cast(deferred_key));
builder_.AppendCharacter(':');
if (gap_ != nullptr) builder_.AppendCharacter(' ');
}
Result SerializeSmi(Smi* object);
......@@ -98,6 +101,14 @@ class BasicJsonStringifier BASE_EMBEDDED {
template <typename Char>
INLINE(static bool DoNotEscape(Char c));
INLINE(void NewLine());
INLINE(void Indent() { indent_++; });
INLINE(void Unindent() { indent_--; });
INLINE(void Separator(bool first) {
if (!first) builder_.AppendCharacter(',');
NewLine();
})
Result StackPush(Handle<Object> object);
void StackPop();
......@@ -107,6 +118,9 @@ class BasicJsonStringifier BASE_EMBEDDED {
IncrementalStringBuilder builder_;
Handle<String> tojson_string_;
Handle<JSArray> stack_;
Handle<String> gap_string_;
uc16* gap_;
int indent_;
static const int kJsonEscapeTableEntrySize = 8;
static const char* const JsonEscapeTable;
......@@ -181,11 +195,26 @@ const char* const BasicJsonStringifier::JsonEscapeTable =
"\370\0 \371\0 \372\0 \373\0 "
"\374\0 \375\0 \376\0 \377\0 ";
BasicJsonStringifier::BasicJsonStringifier(Isolate* isolate)
: isolate_(isolate), builder_(isolate) {
BasicJsonStringifier::BasicJsonStringifier(Isolate* isolate, Handle<String> gap)
: isolate_(isolate), builder_(isolate), gap_string_(gap), indent_(0) {
tojson_string_ = factory()->toJSON_string();
stack_ = factory()->NewJSArray(8);
int gap_length = gap->length();
if (gap_length != 0) {
String::Flatten(gap);
DisallowHeapAllocation no_gc;
String::FlatContent flat = gap->GetFlatContent();
gap_ = NewArray<uc16>(gap_length + 1);
if (flat.IsOneByte()) {
CopyChars(gap_, flat.ToOneByteVector().start(), gap_length);
} else {
CopyChars(gap_, flat.ToUC16Vector().start(), gap_length);
builder_.ChangeEncoding();
}
gap_[gap_length] = '\0';
} else {
gap_ = nullptr;
}
}
......@@ -206,7 +235,8 @@ MaybeHandle<Object> BasicJsonStringifier::StringifyString(
object->length() * kJsonQuoteWorstCaseBlowup + kSpaceForQuotes;
if (worst_case_length > 32 * KB) { // Slow path if too large.
BasicJsonStringifier stringifier(isolate);
BasicJsonStringifier stringifier(isolate,
isolate->factory()->empty_string());
return stringifier.Stringify(object);
}
......@@ -361,11 +391,12 @@ BasicJsonStringifier::Result BasicJsonStringifier::SerializeGeneric(
bool deferred_comma,
bool deferred_key) {
Handle<JSFunction> fun = isolate_->json_serialize_adapter();
Handle<Object> argv[] = { key, object };
Handle<Object> indent(Smi::FromInt(indent_), isolate_);
Handle<Object> argv[] = {key, object, indent, gap_string_};
Handle<Object> result;
ASSIGN_RETURN_ON_EXCEPTION_VALUE(
isolate_, result, Execution::Call(isolate_, fun, object, 2, argv),
EXCEPTION);
isolate_, result,
Execution::Call(isolate_, fun, object, arraysize(argv), argv), EXCEPTION);
if (result->IsUndefined()) return UNCHANGED;
if (deferred_key) {
if (key->IsSmi()) key = factory()->NumberToString(key);
......@@ -436,12 +467,13 @@ BasicJsonStringifier::Result BasicJsonStringifier::SerializeJSArray(
uint32_t length = 0;
CHECK(object->length()->ToArrayLength(&length));
builder_.AppendCharacter('[');
Indent();
switch (object->GetElementsKind()) {
case FAST_SMI_ELEMENTS: {
Handle<FixedArray> elements(FixedArray::cast(object->elements()),
isolate_);
for (uint32_t i = 0; i < length; i++) {
if (i > 0) builder_.AppendCharacter(',');
Separator(i == 0);
SerializeSmi(Smi::cast(elements->get(i)));
}
break;
......@@ -452,7 +484,7 @@ BasicJsonStringifier::Result BasicJsonStringifier::SerializeJSArray(
Handle<FixedDoubleArray> elements(
FixedDoubleArray::cast(object->elements()), isolate_);
for (uint32_t i = 0; i < length; i++) {
if (i > 0) builder_.AppendCharacter(',');
Separator(i == 0);
SerializeDouble(elements->get_scalar(i));
}
break;
......@@ -466,7 +498,7 @@ BasicJsonStringifier::Result BasicJsonStringifier::SerializeJSArray(
if (result != SUCCESS) return result;
break;
}
if (i > 0) builder_.AppendCharacter(',');
Separator(i == 0);
Result result = SerializeElement(
isolate_,
Handle<Object>(FixedArray::cast(object->elements())->get(i),
......@@ -489,6 +521,8 @@ BasicJsonStringifier::Result BasicJsonStringifier::SerializeJSArray(
break;
}
}
Unindent();
if (length > 0) NewLine();
builder_.AppendCharacter(']');
StackPop();
return SUCCESS;
......@@ -498,7 +532,7 @@ BasicJsonStringifier::Result BasicJsonStringifier::SerializeJSArray(
BasicJsonStringifier::Result BasicJsonStringifier::SerializeJSArraySlow(
Handle<JSArray> object, uint32_t start, uint32_t length) {
for (uint32_t i = start; i < length; i++) {
if (i > 0) builder_.AppendCharacter(',');
Separator(i == 0);
Handle<Object> element;
ASSIGN_RETURN_ON_EXCEPTION_VALUE(
isolate_, element, JSReceiver::GetElement(isolate_, object, i),
......@@ -527,6 +561,7 @@ BasicJsonStringifier::Result BasicJsonStringifier::SerializeJSObject(
DCHECK(!object->IsJSGlobalProxy() && !object->IsJSGlobalObject());
builder_.AppendCharacter('{');
Indent();
bool comma = false;
if (object->HasFastProperties() &&
......@@ -593,7 +628,8 @@ BasicJsonStringifier::Result BasicJsonStringifier::SerializeJSObject(
if (result == EXCEPTION) return result;
}
}
Unindent();
if (comma) NewLine();
builder_.AppendCharacter('}');
StackPop();
return SUCCESS;
......@@ -661,6 +697,11 @@ bool BasicJsonStringifier::DoNotEscape(uint16_t c) {
return c >= '#' && c != '\\' && c != 0x7f;
}
void BasicJsonStringifier::NewLine() {
if (gap_ == nullptr) return;
builder_.AppendCharacter('\n');
for (int i = 0; i < indent_; i++) builder_.AppendCString(gap_);
}
void BasicJsonStringifier::SerializeString(Handle<String> object) {
object = String::Flatten(object);
......
......@@ -25,11 +25,12 @@ RUNTIME_FUNCTION(Runtime_QuoteJSONString) {
RUNTIME_FUNCTION(Runtime_BasicJSONStringify) {
HandleScope scope(isolate);
DCHECK(args.length() == 1);
DCHECK(args.length() == 2);
CONVERT_ARG_HANDLE_CHECKED(Object, object, 0);
CONVERT_ARG_HANDLE_CHECKED(String, gap, 1);
Handle<Object> result;
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
isolate, result, Runtime::BasicJsonStringify(isolate, object));
isolate, result, Runtime::BasicJsonStringify(isolate, object, gap));
return *result;
}
......
......@@ -227,8 +227,9 @@ MaybeHandle<Object> Runtime::SetObjectProperty(Isolate* isolate,
}
MaybeHandle<Object> Runtime::BasicJsonStringify(Isolate* isolate,
Handle<Object> object) {
return BasicJsonStringifier(isolate).Stringify(object);
Handle<Object> object,
Handle<String> gap) {
return BasicJsonStringifier(isolate, gap).Stringify(object);
}
MaybeHandle<Object> Runtime::BasicJsonStringifyString(Isolate* isolate,
......
......@@ -324,10 +324,9 @@ namespace internal {
#define FOR_EACH_INTRINSIC_JSON(F) \
F(QuoteJSONString, 1, 1) \
F(BasicJSONStringify, 1, 1) \
F(BasicJSONStringify, 2, 1) \
F(ParseJson, 1, 1)
#define FOR_EACH_INTRINSIC_LITERALS(F) \
F(CreateRegExpLiteral, 4, 1) \
F(CreateObjectLiteral, 4, 1) \
......@@ -1094,7 +1093,7 @@ class Runtime : public AllStatic {
Isolate* isolate, Handle<Object> object, Handle<Object> key);
MUST_USE_RESULT static MaybeHandle<Object> BasicJsonStringify(
Isolate* isolate, Handle<Object> object);
Isolate* isolate, Handle<Object> object, Handle<String> gap);
MUST_USE_RESULT static MaybeHandle<Object> BasicJsonStringifyString(
Isolate* isolate, Handle<String> string);
......
......@@ -293,6 +293,14 @@ class IncrementalStringBuilder {
}
}
INLINE(void AppendCString(const uc16* s)) {
if (encoding_ == String::ONE_BYTE_ENCODING) {
while (*s != '\0') Append<uc16, uint8_t>(*(s++));
} else {
while (*s != '\0') Append<uc16, uc16>(*(s++));
}
}
INLINE(bool CurrentPartCanFit(int length)) {
return part_length_ - current_index_ > length;
}
......
......@@ -22048,6 +22048,20 @@ THREADED_TEST(JSONStringifyObject) {
ExpectString("JSON.stringify(obj)", *utf8);
}
THREADED_TEST(JSONStringifyObjectWithGap) {
LocalContext context;
HandleScope scope(context->GetIsolate());
Local<Value> value =
v8::JSON::Parse(context.local(), v8_str("{\"x\":42}")).ToLocalChecked();
Local<Object> obj = value->ToObject(context.local()).ToLocalChecked();
Local<Object> global = context->Global();
global->Set(context.local(), v8_str("obj"), obj).FromJust();
Local<String> json =
v8::JSON::Stringify(context.local(), obj, v8_str("*")).ToLocalChecked();
v8::String::Utf8Value utf8(json);
ExpectString("JSON.stringify(obj, null, '*')", *utf8);
}
#if V8_OS_POSIX && !V8_OS_NACL
class ThreadInterruptTest {
public:
......
......@@ -35,7 +35,10 @@ function testStringify(expected, object) {
// Test fast case that bails out to slow case.
assertEquals(expected, JSON.stringify(object));
// Test slow case.
assertEquals(expected, JSON.stringify(object, undefined, 0));
assertEquals(expected, JSON.stringify(object, (key, value) => value));
// Test gap.
assertEquals(JSON.stringify(object, null, "="),
JSON.stringify(object, (key, value) => value, "="));
}
......@@ -67,6 +70,7 @@ testStringify('[1,null]', [1, proxy_fun]);
var parent1a = { b: proxy1 };
testStringify('{"b":{"a":"A","b":"B","c":"C"}}', parent1a);
testStringify('{"b":{"a":"A","b":"B","c":"C"}}', parent1a);
var parent1b = { a: 123, b: proxy1, c: true };
testStringify('{"a":123,"b":{"a":"A","b":"B","c":"C"},"c":true}', parent1b);
......
......@@ -555,7 +555,9 @@ TestContext();
function TestStringify(expected, input) {
assertEquals(expected, JSON.stringify(input));
assertEquals(expected, JSON.stringify(input, null, 0));
assertEquals(expected, JSON.stringify(input, (key, value) => value));
assertEquals(JSON.stringify(input, null, "="),
JSON.stringify(input, (key, value) => value, "="));
}
TestStringify(undefined, Symbol("a"));
......
......@@ -622,7 +622,9 @@ TestSIMDObject()
function TestStringify(expected, input) {
assertEquals(expected, JSON.stringify(input));
assertEquals(expected, JSON.stringify(input, null, 0));
assertEquals(expected, JSON.stringify(input, (key, value) => value));
assertEquals(JSON.stringify(input, null, "="),
JSON.stringify(input, (key, value) => value, "="));
}
TestStringify(undefined, SIMD.Float32x4(1, 2, 3, 4));
......
......@@ -234,7 +234,9 @@ TestInvalid('"Garbage""After string"');
function TestStringify(expected, input) {
assertEquals(expected, JSON.stringify(input));
assertEquals(expected, JSON.stringify(input, null, 0));
assertEquals(expected, JSON.stringify(input, (key, value) => value));
assertEquals(JSON.stringify(input, null, "="),
JSON.stringify(input, (key, value) => value, "="));
}
TestStringify("true", true);
......@@ -451,8 +453,8 @@ var counter = { get toJSON() { getCount++;
// RegExps are not callable, so they are stringified as objects.
TestStringify('{}', /regexp/);
TestStringify('42', counter);
assertEquals(2, getCount);
assertEquals(2, callCount);
assertEquals(4, getCount);
assertEquals(4, callCount);
var oddball2 = Object(42);
var oddball3 = Object("foo");
......
......@@ -35,7 +35,9 @@ assertTrue(JSON.stringify(this, null, 0).indexOf('"a":12345') > 0);
// Test JSON.stringify of array in dictionary mode.
function TestStringify(expected, input) {
assertEquals(expected, JSON.stringify(input));
assertEquals(expected, JSON.stringify(input, null, 0));
assertEquals(expected, JSON.stringify(input, (key, value) => value));
assertEquals(JSON.stringify(input, null, "="),
JSON.stringify(input, (key, value) => value, "="));
}
var array_1 = [];
......@@ -76,7 +78,7 @@ var getter_obj = { get getter() {
return 123;
} };
TestStringify('{"getter":123}', getter_obj);
assertEquals(2, counter);
assertEquals(4, counter);
// Test toJSON function.
var tojson_obj = { toJSON: function() {
......@@ -85,7 +87,7 @@ var tojson_obj = { toJSON: function() {
},
a: 1};
TestStringify('[1,2]', tojson_obj);
assertEquals(4, counter);
assertEquals(8, counter);
// Test that we don't recursively look for the toJSON function.
var tojson_proto_obj = { a: 'fail' };
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment