123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361 |
- using System.Collections.Generic;
- using System.Linq;
- using Jint.Native.Array;
- using Jint.Native.Global;
- using Jint.Native.Object;
- using Jint.Runtime;
- using Jint.Runtime.Descriptors;
- namespace Jint.Native.Json
- {
- public class JsonSerializer
- {
- private readonly Engine _engine;
- public JsonSerializer(Engine engine)
- {
- _engine = engine;
- }
- Stack<object> _stack;
- string _indent, _gap;
- List<string> _propertyList;
- JsValue _replacerFunction = Undefined.Instance;
- public JsValue Serialize(JsValue value, JsValue replacer, JsValue space)
- {
- _stack = new Stack<object>();
- // for JSON.stringify(), any function passed as the first argument will return undefined
- // if the replacer is not defined. The function is not called either.
- if (value.Is<ICallable>() && ReferenceEquals(replacer, Undefined.Instance))
- {
- return Undefined.Instance;
- }
- if (replacer.IsObject())
- {
- if (replacer.Is<ICallable>())
- {
- _replacerFunction = replacer;
- }
- else
- {
- var replacerObj = replacer.AsObject();
- if (replacerObj.Class == "Array")
- {
- _propertyList = new List<string>();
- }
- foreach (var property in replacerObj.GetOwnProperties().Select(x => x.Value))
- {
- JsValue v = _engine.GetValue(property, false);
- string item = null;
- if (v.IsString())
- {
- item = v.AsStringWithoutTypeCheck();
- }
- else if (v.IsNumber())
- {
- item = TypeConverter.ToString(v);
- }
- else if (v.IsObject())
- {
- var propertyObj = v.AsObject();
- if (propertyObj.Class == "String" || propertyObj.Class == "Number")
- {
- item = TypeConverter.ToString(v);
- }
- }
- if (item != null && !_propertyList.Contains(item))
- {
- _propertyList.Add(item);
- }
- }
- }
- }
- if (space.IsObject())
- {
- var spaceObj = space.AsObject();
- if (spaceObj.Class == "Number")
- {
- space = TypeConverter.ToNumber(spaceObj);
- }
- else if (spaceObj.Class == "String")
- {
- space = TypeConverter.ToString(spaceObj);
- }
- }
- // defining the gap
- if (space.IsNumber())
- {
- var number = ((JsNumber) space)._value;
- if (number > 0)
- {
- _gap = new string(' ', (int) System.Math.Min(10, number));
- }
- else
- {
- _gap = string.Empty;
- }
- }
- else if (space.IsString())
- {
- var stringSpace = space.AsStringWithoutTypeCheck();
- _gap = stringSpace.Length <= 10 ? stringSpace : stringSpace.Substring(0, 10);
- }
- else
- {
- _gap = string.Empty;
- }
- var wrapper = _engine.Object.Construct(Arguments.Empty);
- wrapper.DefineOwnProperty("", new PropertyDescriptor(value, PropertyFlag.ConfigurableEnumerableWritable), false);
- return Str("", wrapper);
- }
- private JsValue Str(string key, ObjectInstance holder)
- {
- var value = holder.Get(key);
- if (value.IsObject())
- {
- var toJson = value.AsObject().Get("toJSON");
- if (toJson.IsObject())
- {
- var callableToJson = toJson.AsObject() as ICallable;
- if (callableToJson != null)
- {
- value = callableToJson.Call(value, Arguments.From(key));
- }
- }
- }
- if (!ReferenceEquals(_replacerFunction, Undefined.Instance))
- {
- var replacerFunctionCallable = (ICallable)_replacerFunction.AsObject();
- value = replacerFunctionCallable.Call(holder, Arguments.From(key, value));
- }
- if (value.IsObject())
- {
- var valueObj = value.AsObject();
- switch (valueObj.Class)
- {
- case "Number":
- value = TypeConverter.ToNumber(value);
- break;
- case "String":
- value = TypeConverter.ToString(value);
- break;
- case "Boolean":
- value = TypeConverter.ToPrimitive(value);
- break;
- case "Array":
- value = SerializeArray(value.As<ArrayInstance>());
- return value;
- case "Object":
- value = SerializeObject(value.AsObject());
- return value;
- }
- }
- if (ReferenceEquals(value, Null.Instance))
- {
- return "null";
- }
- if (value.IsBoolean())
- {
- return ((JsBoolean) value)._value ? "true" : "false";
- }
- if (value.IsString())
- {
- return Quote(value.AsStringWithoutTypeCheck());
- }
- if (value.IsNumber())
- {
- var isFinite = GlobalObject.IsFinite(Undefined.Instance, Arguments.From(value));
- if (((JsBoolean) isFinite)._value)
- {
- return TypeConverter.ToString(((JsNumber) value)._value);
- }
- return "null";
- }
- var isCallable = value.IsObject() && value.AsObject() is ICallable;
- if (value.IsObject() && isCallable == false)
- {
- if (value.AsObject().Class == "Array")
- {
- return SerializeArray(value.As<ArrayInstance>());
- }
- return SerializeObject(value.AsObject());
- }
- return JsValue.Undefined;
- }
- private static string Quote(string value)
- {
- var sb = new System.Text.StringBuilder("\"");
- foreach (char c in value)
- {
- switch (c)
- {
- case '\"':
- sb.Append("\\\"");
- break;
- case '\\':
- sb.Append("\\\\");
- break;
- case '\b':
- sb.Append("\\b");
- break;
- case '\f':
- sb.Append("\\f");
- break;
- case '\n':
- sb.Append("\\n");
- break;
- case '\r':
- sb.Append("\\r");
- break;
- case '\t':
- sb.Append("\\t");
- break;
- default:
- if (c < 0x20)
- {
- sb.Append("\\u");
- sb.Append(((int) c).ToString("x4"));
- }
- else
- sb.Append(c);
- break;
- }
- }
- sb.Append("\"");
- return sb.ToString();
- }
- private string SerializeArray(ArrayInstance value)
- {
- EnsureNonCyclicity(value);
- _stack.Push(value);
- var stepback = _indent;
- _indent = _indent + _gap;
- var partial = new List<string>();
- var len = TypeConverter.ToUint32(value.Get("length"));
- for (int i = 0; i < len; i++)
- {
- var strP = Str(TypeConverter.ToString(i), value);
- if (strP.IsUndefined())
- strP = "null";
- partial.Add(strP.AsStringWithoutTypeCheck());
- }
- if (partial.Count == 0)
- {
- _stack.Pop();
- return "[]";
- }
- string final;
- if (_gap == "")
- {
- var separator = ",";
- var properties = System.String.Join(separator, partial.ToArray());
- final = "[" + properties + "]";
- }
- else
- {
- var separator = ",\n" + _indent;
- var properties = System.String.Join(separator, partial.ToArray());
- final = "[\n" + _indent + properties + "\n" + stepback + "]";
- }
- _stack.Pop();
- _indent = stepback;
- return final;
- }
- private void EnsureNonCyclicity(object value)
- {
- if (value == null)
- {
- ExceptionHelper.ThrowArgumentNullException(nameof(value));
- }
- if (_stack.Contains(value))
- {
- ExceptionHelper.ThrowTypeError(_engine, "Cyclic reference detected.");
- }
- }
- private string SerializeObject(ObjectInstance value)
- {
- string final;
- EnsureNonCyclicity(value);
- _stack.Push(value);
- var stepback = _indent;
- _indent += _gap;
- var k = _propertyList ?? value.GetOwnProperties()
- .Where(x => x.Value.Enumerable)
- .Select(x => x.Key)
- .ToList();
- var partial = new List<string>();
- foreach (var p in k)
- {
- var strP = Str(p, value);
- if (!strP.IsUndefined())
- {
- var member = Quote(p) + ":";
- if (_gap != "")
- {
- member += " ";
- }
- member += strP.AsString(); // TODO:This could be undefined
- partial.Add(member);
- }
- }
- if (partial.Count == 0)
- {
- final = "{}";
- }
- else
- {
- if (_gap == "")
- {
- var separator = ",";
- var properties = System.String.Join(separator, partial.ToArray());
- final = "{" + properties + "}";
- }
- else
- {
- var separator = ",\n" + _indent;
- var properties = System.String.Join(separator, partial.ToArray());
- final = "{\n" + _indent + properties + "\n" + stepback + "}";
- }
- }
- _stack.Pop();
- _indent = stepback;
- return final;
- }
- }
- }
|