292 lines
6.6 KiB
Haxe
292 lines
6.6 KiB
Haxe
|
/*
|
||
|
* Copyright (C)2005-2019 Haxe Foundation
|
||
|
*
|
||
|
* Permission is hereby granted, free of charge, to any person obtaining a
|
||
|
* copy of this software and associated documentation files (the "Software"),
|
||
|
* to deal in the Software without restriction, including without limitation
|
||
|
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||
|
* and/or sell copies of the Software, and to permit persons to whom the
|
||
|
* Software is furnished to do so, subject to the following conditions:
|
||
|
*
|
||
|
* The above copyright notice and this permission notice shall be included in
|
||
|
* all copies or substantial portions of the Software.
|
||
|
*
|
||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||
|
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||
|
* DEALINGS IN THE SOFTWARE.
|
||
|
*/
|
||
|
|
||
|
package haxe.format;
|
||
|
|
||
|
/**
|
||
|
An implementation of JSON printer in Haxe.
|
||
|
|
||
|
This class is used by `haxe.Json` when native JSON implementation
|
||
|
is not available.
|
||
|
|
||
|
@see https://haxe.org/manual/std-Json-encoding.html
|
||
|
**/
|
||
|
class JsonPrinter {
|
||
|
/**
|
||
|
Encodes `o`'s value and returns the resulting JSON string.
|
||
|
|
||
|
If `replacer` is given and is not null, it is used to retrieve
|
||
|
actual object to be encoded. The `replacer` function takes two parameters,
|
||
|
the key and the value being encoded. Initial key value is an empty string.
|
||
|
|
||
|
If `space` is given and is not null, the result will be pretty-printed.
|
||
|
Successive levels will be indented by this string.
|
||
|
**/
|
||
|
static public function print(o:Dynamic, ?replacer:(key:Dynamic, value:Dynamic) -> Dynamic, ?space:String):String {
|
||
|
var printer = new JsonPrinter(replacer, space);
|
||
|
printer.write("", o);
|
||
|
return printer.buf.toString();
|
||
|
}
|
||
|
|
||
|
var buf:#if flash flash.utils.ByteArray #else StringBuf #end;
|
||
|
var replacer:(key:Dynamic, value:Dynamic) -> Dynamic;
|
||
|
var indent:String;
|
||
|
var pretty:Bool;
|
||
|
var nind:Int;
|
||
|
|
||
|
function new(replacer:(key:Dynamic, value:Dynamic) -> Dynamic, space:String) {
|
||
|
this.replacer = replacer;
|
||
|
this.indent = space;
|
||
|
this.pretty = space != null;
|
||
|
this.nind = 0;
|
||
|
|
||
|
#if flash
|
||
|
buf = new flash.utils.ByteArray();
|
||
|
buf.endian = flash.utils.Endian.BIG_ENDIAN;
|
||
|
buf.position = 0;
|
||
|
#else
|
||
|
buf = new StringBuf();
|
||
|
#end
|
||
|
}
|
||
|
|
||
|
inline function ipad():Void {
|
||
|
if (pretty)
|
||
|
add(StringTools.lpad('', indent, nind * indent.length));
|
||
|
}
|
||
|
|
||
|
inline function newl():Void {
|
||
|
if (pretty)
|
||
|
addChar('\n'.code);
|
||
|
}
|
||
|
|
||
|
function write(k:Dynamic, v:Dynamic) {
|
||
|
if (replacer != null)
|
||
|
v = replacer(k, v);
|
||
|
switch (Type.typeof(v)) {
|
||
|
case TUnknown:
|
||
|
add('"???"');
|
||
|
case TObject:
|
||
|
objString(v);
|
||
|
case TInt:
|
||
|
add(#if (jvm || hl) Std.string(v) #else v #end);
|
||
|
case TFloat:
|
||
|
add(Math.isFinite(v) ? Std.string(v) : 'null');
|
||
|
case TFunction:
|
||
|
add('"<fun>"');
|
||
|
case TClass(c):
|
||
|
if (c == String)
|
||
|
quote(v);
|
||
|
else if (c == Array) {
|
||
|
var v:Array<Dynamic> = v;
|
||
|
addChar('['.code);
|
||
|
|
||
|
var len = v.length;
|
||
|
var last = len - 1;
|
||
|
for (i in 0...len) {
|
||
|
if (i > 0)
|
||
|
addChar(','.code)
|
||
|
else
|
||
|
nind++;
|
||
|
newl();
|
||
|
ipad();
|
||
|
write(i, v[i]);
|
||
|
if (i == last) {
|
||
|
nind--;
|
||
|
newl();
|
||
|
ipad();
|
||
|
}
|
||
|
}
|
||
|
addChar(']'.code);
|
||
|
} else if (c == haxe.ds.StringMap) {
|
||
|
var v:haxe.ds.StringMap<Dynamic> = v;
|
||
|
var o = {};
|
||
|
for (k in v.keys())
|
||
|
Reflect.setField(o, k, v.get(k));
|
||
|
objString(o);
|
||
|
} else if (c == Date) {
|
||
|
var v:Date = v;
|
||
|
quote(v.toString());
|
||
|
} else
|
||
|
classString(v);
|
||
|
case TEnum(_):
|
||
|
var i:Dynamic = Type.enumIndex(v);
|
||
|
add(i);
|
||
|
case TBool:
|
||
|
add(#if (php || jvm || hl) (v ? 'true' : 'false') #else v #end);
|
||
|
case TNull:
|
||
|
add('null');
|
||
|
}
|
||
|
}
|
||
|
|
||
|
extern inline function addChar(c:Int) {
|
||
|
#if flash
|
||
|
buf.writeByte(c);
|
||
|
#else
|
||
|
buf.addChar(c);
|
||
|
#end
|
||
|
}
|
||
|
|
||
|
extern inline function add(v:String) {
|
||
|
#if flash
|
||
|
// argument is not always a string but will be automatically casted
|
||
|
buf.writeUTFBytes(v);
|
||
|
#else
|
||
|
buf.add(v);
|
||
|
#end
|
||
|
}
|
||
|
|
||
|
function classString(v:Dynamic) {
|
||
|
fieldsString(v, Type.getInstanceFields(Type.getClass(v)));
|
||
|
}
|
||
|
|
||
|
inline function objString(v:Dynamic) {
|
||
|
fieldsString(v, Reflect.fields(v));
|
||
|
}
|
||
|
|
||
|
function fieldsString(v:Dynamic, fields:Array<String>) {
|
||
|
addChar('{'.code);
|
||
|
var len = fields.length;
|
||
|
var last = len - 1;
|
||
|
var first = true;
|
||
|
for (i in 0...len) {
|
||
|
var f = fields[i];
|
||
|
var value = Reflect.field(v, f);
|
||
|
if (Reflect.isFunction(value))
|
||
|
continue;
|
||
|
if (first) {
|
||
|
nind++;
|
||
|
first = false;
|
||
|
} else
|
||
|
addChar(','.code);
|
||
|
newl();
|
||
|
ipad();
|
||
|
quote(f);
|
||
|
addChar(':'.code);
|
||
|
if (pretty)
|
||
|
addChar(' '.code);
|
||
|
write(f, value);
|
||
|
if (i == last) {
|
||
|
nind--;
|
||
|
newl();
|
||
|
ipad();
|
||
|
}
|
||
|
}
|
||
|
addChar('}'.code);
|
||
|
}
|
||
|
|
||
|
function quote(s:String) {
|
||
|
#if neko
|
||
|
if (s.length != neko.Utf8.length(s)) {
|
||
|
quoteUtf8(s);
|
||
|
return;
|
||
|
}
|
||
|
#end
|
||
|
addChar('"'.code);
|
||
|
var i = 0;
|
||
|
var length = s.length;
|
||
|
#if hl
|
||
|
var prev = -1;
|
||
|
#end
|
||
|
while (i < length) {
|
||
|
var c = StringTools.unsafeCodeAt(s, i++);
|
||
|
switch (c) {
|
||
|
case '"'.code:
|
||
|
add('\\"');
|
||
|
case '\\'.code:
|
||
|
add('\\\\');
|
||
|
case '\n'.code:
|
||
|
add('\\n');
|
||
|
case '\r'.code:
|
||
|
add('\\r');
|
||
|
case '\t'.code:
|
||
|
add('\\t');
|
||
|
case 8:
|
||
|
add('\\b');
|
||
|
case 12:
|
||
|
add('\\f');
|
||
|
default:
|
||
|
#if flash
|
||
|
if (c >= 128)
|
||
|
add(String.fromCharCode(c))
|
||
|
else
|
||
|
addChar(c);
|
||
|
#elseif hl
|
||
|
if (prev >= 0) {
|
||
|
if (c >= 0xD800 && c <= 0xDFFF) {
|
||
|
addChar((((prev - 0xD800) << 10) | (c - 0xDC00)) + 0x10000);
|
||
|
prev = -1;
|
||
|
} else {
|
||
|
addChar("□".code);
|
||
|
prev = c;
|
||
|
}
|
||
|
} else {
|
||
|
if (c >= 0xD800 && c <= 0xDFFF)
|
||
|
prev = c;
|
||
|
else
|
||
|
addChar(c);
|
||
|
}
|
||
|
#else
|
||
|
addChar(c);
|
||
|
#end
|
||
|
}
|
||
|
}
|
||
|
#if hl
|
||
|
if (prev >= 0)
|
||
|
addChar("□".code);
|
||
|
#end
|
||
|
addChar('"'.code);
|
||
|
}
|
||
|
|
||
|
#if neko
|
||
|
function quoteUtf8(s:String) {
|
||
|
var u = new neko.Utf8();
|
||
|
neko.Utf8.iter(s, function(c) {
|
||
|
switch (c) {
|
||
|
case '\\'.code, '"'.code:
|
||
|
u.addChar('\\'.code);
|
||
|
u.addChar(c);
|
||
|
case '\n'.code:
|
||
|
u.addChar('\\'.code);
|
||
|
u.addChar('n'.code);
|
||
|
case '\r'.code:
|
||
|
u.addChar('\\'.code);
|
||
|
u.addChar('r'.code);
|
||
|
case '\t'.code:
|
||
|
u.addChar('\\'.code);
|
||
|
u.addChar('t'.code);
|
||
|
case 8:
|
||
|
u.addChar('\\'.code);
|
||
|
u.addChar('b'.code);
|
||
|
case 12:
|
||
|
u.addChar('\\'.code);
|
||
|
u.addChar('f'.code);
|
||
|
default:
|
||
|
u.addChar(c);
|
||
|
}
|
||
|
});
|
||
|
buf.add('"');
|
||
|
buf.add(u.toString());
|
||
|
buf.add('"');
|
||
|
}
|
||
|
#end
|
||
|
}
|