diff options
18 files changed, 5023 insertions, 18 deletions
diff --git a/mcs/class/System.Json/Makefile b/mcs/class/System.Json/Makefile index f83e3ed6a98..ed5a59985ca 100644 --- a/mcs/class/System.Json/Makefile +++ b/mcs/class/System.Json/Makefile @@ -2,7 +2,7 @@ thisdir = class/System.Json SUBDIRS = include ../../build/rules.make -System.Json.Properties.Resources.resources: ../../../external/aspnetwebstack/src/System.Json/Properties/Resources.resx +System.Json.Properties.Resources.resources: System.Json/Properties/Resources.resx $(RESGEN) "$<" "$@" LIBRARY = System.Json.dll @@ -15,7 +15,11 @@ LIB_MCS_FLAGS = /d:ASPNETMVC -keyfile:../winfx.pub -delaysign \ /resource:System.Json.Properties.Resources.resources ifeq (4, $(FRAMEWORK_VERSION_MAJOR)) -LIB_MCS_FLAGS += /r:Microsoft.CSharp.dll +LIB_MCS_FLAGS += /r:Microsoft.CSharp.dll -d:FEATURE_DYNAMIC +endif + +ifeq (monodroid, $(PROFILE)) +LIB_MCS_FLAGS += -d:FEATURE_DYNAMIC endif TEST_MCS_FLAGS = $(LIB_MCS_FLAGS) diff --git a/mcs/class/System.Json/System.Json.dll.sources b/mcs/class/System.Json/System.Json.dll.sources index f829400c4a5..4d2f589c7ab 100644 --- a/mcs/class/System.Json/System.Json.dll.sources +++ b/mcs/class/System.Json/System.Json.dll.sources @@ -5,19 +5,18 @@ Assembly/AssemblyInfo.cs ../../../external/aspnetwebstack/src/CommonAssemblyInfo.cs ../../../external/aspnetwebstack/src/TransparentCommonAssemblyInfo.cs -../../../external/aspnetwebstack/src/System.Json/Properties/AssemblyInfo.cs -../../../external/aspnetwebstack/src/System.Json/Extensions/JsonValueExtensions.cs -../../../external/aspnetwebstack/src/System.Json/GlobalSuppressions.cs -../../../external/aspnetwebstack/src/System.Json/JXmlToJsonValueConverter.cs -../../../external/aspnetwebstack/src/System.Json/JsonArray.cs -../../../external/aspnetwebstack/src/System.Json/JsonObject.cs -../../../external/aspnetwebstack/src/System.Json/JsonPrimitive.cs -../../../external/aspnetwebstack/src/System.Json/JsonType.cs -../../../external/aspnetwebstack/src/System.Json/JsonValue.cs -../../../external/aspnetwebstack/src/System.Json/JsonValueChange.cs -../../../external/aspnetwebstack/src/System.Json/JsonValueChangeEventArgs.cs -../../../external/aspnetwebstack/src/System.Json/JsonValueDynamicMetaObject.cs -../../../external/aspnetwebstack/src/System.Json/JsonValueLinqExtensions.cs -../../../external/aspnetwebstack/src/System.Json/NGenWrapper.cs -../../../external/aspnetwebstack/src/System.Json/Properties/Resources.Designer.cs - +System.Json/Properties/AssemblyInfo.cs +System.Json/Extensions/JsonValueExtensions.cs +System.Json/GlobalSuppressions.cs +System.Json/JXmlToJsonValueConverter.cs +System.Json/JsonArray.cs +System.Json/JsonObject.cs +System.Json/JsonPrimitive.cs +System.Json/JsonType.cs +System.Json/JsonValue.cs +System.Json/JsonValueChange.cs +System.Json/JsonValueChangeEventArgs.cs +System.Json/JsonValueDynamicMetaObject.cs +System.Json/JsonValueLinqExtensions.cs +System.Json/NGenWrapper.cs +System.Json/Properties/Resources.Designer.cs diff --git a/mcs/class/System.Json/System.Json/Extensions/JsonValueExtensions.cs b/mcs/class/System.Json/System.Json/Extensions/JsonValueExtensions.cs new file mode 100644 index 00000000000..2ea0c14bbc1 --- /dev/null +++ b/mcs/class/System.Json/System.Json/Extensions/JsonValueExtensions.cs @@ -0,0 +1,383 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +#if FEATURE_DYNAMIC +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; +using System.Dynamic; +using System.IO; +using System.Json; +using System.Linq.Expressions; + +namespace System.Runtime.Serialization.Json +{ + /// <summary> + /// This class extends the functionality of the <see cref="JsonValue"/> type. + /// </summary> + [EditorBrowsable(EditorBrowsableState.Never)] + public static class JsonValueExtensions + { + /// <summary> + /// Creates a <see cref="System.Json.JsonValue"/> object based on an arbitrary CLR object. + /// </summary> + /// <param name="value">The object to be converted to <see cref="System.Json.JsonValue"/>.</param> + /// <returns>The <see cref="System.Json.JsonValue"/> which represents the given object.</returns> + /// <remarks>The conversion is done through the <see cref="System.Runtime.Serialization.Json.DataContractJsonSerializer"/>; + /// the object is first serialized into JSON using the serializer, then parsed into a <see cref="System.Json.JsonValue"/> + /// object.</remarks> + public static JsonValue CreateFrom(object value) + { + JsonValue jsonValue = null; + + if (value != null) + { + jsonValue = value as JsonValue; + + if (jsonValue == null) + { + jsonValue = JsonValueExtensions.CreatePrimitive(value); + + if (jsonValue == null) + { + jsonValue = JsonValueExtensions.CreateFromDynamic(value); + + if (jsonValue == null) + { + jsonValue = JsonValueExtensions.CreateFromComplex(value); + } + } + } + } + + return jsonValue; + } + + /// <summary> + /// Attempts to convert this <see cref="System.Json.JsonValue"/> instance into the type T. + /// </summary> + /// <typeparam name="T">The type to which the conversion is being performed.</typeparam> + /// <param name="jsonValue">The <see cref="JsonValue"/> instance this method extension is to be applied to.</param> + /// <param name="valueOfT">An instance of T initialized with this instance, or the default + /// value of T, if the conversion cannot be performed.</param> + /// <returns>true if this <see cref="System.Json.JsonValue"/> instance can be read as type T; otherwise, false.</returns> + public static bool TryReadAsType<T>(this JsonValue jsonValue, out T valueOfT) + { + if (jsonValue == null) + { + throw new ArgumentNullException("jsonValue"); + } + + object value; + if (JsonValueExtensions.TryReadAsType(jsonValue, typeof(T), out value)) + { + valueOfT = (T)value; + return true; + } + + valueOfT = default(T); + return false; + } + + /// <summary> + /// Attempts to convert this <see cref="System.Json.JsonValue"/> instance into the type T. + /// </summary> + /// <typeparam name="T">The type to which the conversion is being performed.</typeparam> + /// <param name="jsonValue">The <see cref="JsonValue"/> instance this method extension is to be applied to.</param> + /// <returns>An instance of T initialized with the <see cref="System.Json.JsonValue"/> value + /// specified if the conversion.</returns> + /// <exception cref="System.NotSupportedException">If this <see cref="System.Json.JsonValue"/> value cannot be + /// converted into the type T.</exception> + [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", + Justification = "The generic parameter is used to specify the output type")] + public static T ReadAsType<T>(this JsonValue jsonValue) + { + if (jsonValue == null) + { + throw new ArgumentNullException("jsonValue"); + } + + return (T)JsonValueExtensions.ReadAsType(jsonValue, typeof(T)); + } + + /// <summary> + /// Attempts to convert this <see cref="System.Json.JsonValue"/> instance into the type T, returning a fallback value + /// if the conversion fails. + /// </summary> + /// <typeparam name="T">The type to which the conversion is being performed.</typeparam> + /// <param name="jsonValue">The <see cref="JsonValue"/> instance this method extension is to be applied to.</param> + /// <param name="fallback">A fallback value to be retuned in case the conversion cannot be performed.</param> + /// <returns>An instance of T initialized with the <see cref="System.Json.JsonValue"/> value + /// specified if the conversion succeeds or the specified fallback value if it fails.</returns> + public static T ReadAsType<T>(this JsonValue jsonValue, T fallback) + { + if (jsonValue == null) + { + throw new ArgumentNullException("jsonValue"); + } + + T outVal; + if (JsonValueExtensions.TryReadAsType<T>(jsonValue, out outVal)) + { + return outVal; + } + + return fallback; + } + + /// <summary> + /// Attempts to convert this <see cref="System.Json.JsonValue"/> instance into an instance of the specified type. + /// </summary> + /// <param name="jsonValue">The <see cref="JsonValue"/> instance this method extension is to be applied to.</param> + /// <param name="type">The type to which the conversion is being performed.</param> + /// <returns>An object instance initialized with the <see cref="System.Json.JsonValue"/> value + /// specified if the conversion.</returns> + /// <exception cref="System.NotSupportedException">If this <see cref="System.Json.JsonValue"/> value cannot be + /// converted into the type T.</exception> + public static object ReadAsType(this JsonValue jsonValue, Type type) + { + if (jsonValue == null) + { + throw new ArgumentNullException("jsonValue"); + } + + if (type == null) + { + throw new ArgumentNullException("type"); + } + + object result; + if (JsonValueExtensions.TryReadAsType(jsonValue, type, out result)) + { + return result; + } + + throw new NotSupportedException(RS.Format(System.Json.Properties.Resources.CannotReadAsType, jsonValue.GetType().FullName, type.FullName)); + } + + /// <summary> + /// Attempts to convert this <see cref="System.Json.JsonValue"/> instance into an instance of the specified type. + /// </summary> + /// <param name="jsonValue">The <see cref="JsonValue"/> instance this method extension is to be applied to.</param> + /// <param name="type">The type to which the conversion is being performed.</param> + /// <param name="value">An object to be initialized with this instance or null if the conversion cannot be performed.</param> + /// <returns>true if this <see cref="System.Json.JsonValue"/> instance can be read as the specified type; otherwise, false.</returns> + [SuppressMessage("Microsoft.Design", "CA1007:UseGenericsWhereAppropriate", + Justification = "This is the non-generic version of the method.")] + [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Exception translates to fail.")] + public static bool TryReadAsType(this JsonValue jsonValue, Type type, out object value) + { + if (jsonValue == null) + { + throw new ArgumentNullException("jsonValue"); + } + + if (type == null) + { + throw new ArgumentNullException("type"); + } + + if (type == typeof(JsonValue) || type == typeof(object)) + { + value = jsonValue; + return true; + } + + if (type == typeof(object[]) || type == typeof(Dictionary<string, object>)) + { + if (!JsonValueExtensions.CanConvertToClrCollection(jsonValue, type)) + { + value = null; + return false; + } + } + + if (jsonValue.TryReadAs(type, out value)) + { + return true; + } + + try + { + using (MemoryStream ms = new MemoryStream()) + { + jsonValue.Save(ms); + ms.Position = 0; + DataContractJsonSerializer dcjs = new DataContractJsonSerializer(type); + value = dcjs.ReadObject(ms); + } + + return true; + } + catch (Exception) + { + value = null; + return false; + } + } + + /// <summary> + /// Determines whether the specified <see cref="JsonValue"/> instance can be converted to the specified collection <see cref="Type"/>. + /// </summary> + /// <param name="jsonValue">The instance to be converted.</param> + /// <param name="collectionType">The collection type to convert the instance to.</param> + /// <returns>true if the instance can be converted, false otherwise</returns> + private static bool CanConvertToClrCollection(JsonValue jsonValue, Type collectionType) + { + if (jsonValue != null) + { + return (jsonValue.JsonType == JsonType.Object && collectionType == typeof(Dictionary<string, object>)) || + (jsonValue.JsonType == JsonType.Array && collectionType == typeof(object[])); + } + + return false; + } + + private static JsonValue CreatePrimitive(object value) + { + JsonPrimitive jsonPrimitive; + + if (JsonPrimitive.TryCreate(value, out jsonPrimitive)) + { + return jsonPrimitive; + } + + return null; + } + + private static JsonValue CreateFromComplex(object value) + { + DataContractJsonSerializer dcjs = new DataContractJsonSerializer(value.GetType()); + using (MemoryStream ms = new MemoryStream()) + { + dcjs.WriteObject(ms, value); + ms.Position = 0; + return JsonValue.Load(ms); + } + } + + [SuppressMessage("Microsoft.Performance", "CA1800:DoNotCastUnnecessarily", Justification = "value is not the same")] + private static JsonValue CreateFromDynamic(object value) + { + JsonObject parent = null; + DynamicObject dynObj = value as DynamicObject; + + if (dynObj != null) + { + parent = new JsonObject(); + Stack<CreateFromTypeStackInfo> infoStack = new Stack<CreateFromTypeStackInfo>(); + IEnumerator<string> keys = null; + + do + { + if (keys == null) + { + keys = dynObj.GetDynamicMemberNames().GetEnumerator(); + } + + while (keys.MoveNext()) + { + JsonValue child = null; + string key = keys.Current; + SimpleGetMemberBinder binder = new SimpleGetMemberBinder(key); + + if (dynObj.TryGetMember(binder, out value)) + { + DynamicObject childDynObj = value as DynamicObject; + + if (childDynObj != null) + { + child = new JsonObject(); + parent.Add(key, child); + + infoStack.Push(new CreateFromTypeStackInfo(parent, dynObj, keys)); + + parent = child as JsonObject; + dynObj = childDynObj; + keys = null; + + break; + } + else + { + if (value != null) + { + child = value as JsonValue; + + if (child == null) + { + child = JsonValueExtensions.CreatePrimitive(value); + + if (child == null) + { + child = JsonValueExtensions.CreateFromComplex(value); + } + } + } + + parent.Add(key, child); + } + } + } + + if (infoStack.Count > 0 && keys != null) + { + CreateFromTypeStackInfo info = infoStack.Pop(); + + parent = info.JsonObject; + dynObj = info.DynamicObject; + keys = info.Keys; + } + } + while (infoStack.Count > 0); + } + + return parent; + } + + private class CreateFromTypeStackInfo + { + public CreateFromTypeStackInfo(JsonObject jsonObject, DynamicObject dynamicObject, IEnumerator<string> keyEnumerator) + { + JsonObject = jsonObject; + DynamicObject = dynamicObject; + Keys = keyEnumerator; + } + + /// <summary> + /// Gets of sets + /// </summary> + public JsonObject JsonObject { get; set; } + + /// <summary> + /// Gets of sets + /// </summary> + public DynamicObject DynamicObject { get; set; } + + /// <summary> + /// Gets of sets + /// </summary> + public IEnumerator<string> Keys { get; set; } + } + + private class SimpleGetMemberBinder : GetMemberBinder + { + public SimpleGetMemberBinder(string name) + : base(name, false) + { + } + + public override DynamicMetaObject FallbackGetMember(DynamicMetaObject target, DynamicMetaObject errorSuggestion) + { + if (target != null && errorSuggestion == null) + { + string exceptionMessage = RS.Format(System.Json.Properties.Resources.DynamicPropertyNotDefined, target.LimitType, Name); + Expression throwExpression = Expression.Throw(Expression.Constant(new InvalidOperationException(exceptionMessage)), typeof(object)); + + errorSuggestion = new DynamicMetaObject(throwExpression, target.Restrictions); + } + + return errorSuggestion; + } + } + } +} +#endif diff --git a/mcs/class/System.Json/System.Json/GlobalSuppressions.cs b/mcs/class/System.Json/System.Json/GlobalSuppressions.cs new file mode 100644 index 00000000000..5d3e5578676 --- /dev/null +++ b/mcs/class/System.Json/System.Json/GlobalSuppressions.cs @@ -0,0 +1,6 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. + +using System.Diagnostics.CodeAnalysis; + +[assembly: SuppressMessage("Microsoft.Design", "CA2210:AssembliesShouldHaveValidStrongNames", Justification = "These assemblies are delay-signed.")] +[assembly: SuppressMessage("Microsoft.Design", "CA1020:AvoidNamespacesWithFewTypes", Justification = "Classes are grouped logically for user clarity.", Scope = "namespace", Target = "System.Runtime.Serialization.Json")] diff --git a/mcs/class/System.Json/System.Json/JXmlToJsonValueConverter.cs b/mcs/class/System.Json/System.Json/JXmlToJsonValueConverter.cs new file mode 100644 index 00000000000..f4952646b58 --- /dev/null +++ b/mcs/class/System.Json/System.Json/JXmlToJsonValueConverter.cs @@ -0,0 +1,304 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Runtime.Serialization.Json; +using System.Text; +using System.Xml; + +namespace System.Json +{ + internal static class JXmlToJsonValueConverter + { + internal const string RootElementName = "root"; + internal const string ItemElementName = "item"; + internal const string TypeAttributeName = "type"; + internal const string ArrayAttributeValue = "array"; + internal const string BooleanAttributeValue = "boolean"; + internal const string NullAttributeValue = "null"; + internal const string NumberAttributeValue = "number"; + internal const string ObjectAttributeValue = "object"; + internal const string StringAttributeValue = "string"; + private const string TypeHintAttributeName = "__type"; + + private static readonly char[] _floatingPointChars = new char[] { '.', 'e', 'E' }; + + public static JsonValue JXMLToJsonValue(Stream jsonStream) + { + if (jsonStream == null) + { + throw new ArgumentNullException("jsonStream"); + } + + return JXMLToJsonValue(jsonStream, null); + } + + public static JsonValue JXMLToJsonValue(string jsonString) + { + if (jsonString == null) + { + throw new ArgumentNullException("jsonString"); + } + + if (jsonString.Length == 0) + { + throw new ArgumentException(Properties.Resources.JsonStringCannotBeEmpty, "jsonString"); + } + + byte[] jsonBytes = Encoding.UTF8.GetBytes(jsonString); + + return JXMLToJsonValue(null, jsonBytes); + } + + public static JsonValue JXMLToJsonValue(XmlDictionaryReader jsonReader) + { + if (jsonReader == null) + { + throw new ArgumentNullException("jsonReader"); + } + + const string RootObjectName = "RootObject"; + Stack<JsonValue> jsonStack = new Stack<JsonValue>(); + string nodeType = null; + bool isEmptyElement = false; + + JsonValue parent = new JsonObject(); + jsonStack.Push(parent); + string currentName = RootObjectName; + + try + { + MoveToRootNode(jsonReader); + + while (jsonStack.Count > 0 && jsonReader.NodeType != XmlNodeType.None) + { + if (parent is JsonObject && currentName == null) + { + currentName = GetMemberName(jsonReader); + } + + nodeType = jsonReader.GetAttribute(TypeAttributeName) ?? StringAttributeValue; + + if (parent is JsonArray) + { + // For arrays, the element name has to be "item" + if (jsonReader.Name != ItemElementName) + { + throw new FormatException(Properties.Resources.IncorrectJsonFormat); + } + } + + switch (nodeType) + { + case NullAttributeValue: + case BooleanAttributeValue: + case StringAttributeValue: + case NumberAttributeValue: + JsonPrimitive jsonPrimitive = ReadPrimitive(nodeType, jsonReader); + InsertJsonValue(jsonStack, ref parent, ref currentName, jsonPrimitive, true); + break; + case ArrayAttributeValue: + JsonArray jsonArray = CreateJsonArray(jsonReader, ref isEmptyElement); + InsertJsonValue(jsonStack, ref parent, ref currentName, jsonArray, isEmptyElement); + break; + case ObjectAttributeValue: + JsonObject jsonObject = CreateObjectWithTypeHint(jsonReader, ref isEmptyElement); + InsertJsonValue(jsonStack, ref parent, ref currentName, jsonObject, isEmptyElement); + break; + default: + throw new FormatException(Properties.Resources.IncorrectJsonFormat); + } + + while (jsonReader.NodeType == XmlNodeType.EndElement && jsonStack.Count > 0) + { + jsonReader.Read(); + SkipWhitespace(jsonReader); + jsonStack.Pop(); + if (jsonStack.Count > 0) + { + parent = jsonStack.Peek(); + } + } + } + } + catch (XmlException xmlException) + { + throw new FormatException(Properties.Resources.IncorrectJsonFormat, xmlException); + } + + if (jsonStack.Count != 1) + { + throw new FormatException(Properties.Resources.IncorrectJsonFormat); + } + + return parent[RootObjectName]; + } + + private static JsonValue JXMLToJsonValue(Stream jsonStream, byte[] jsonBytes) + { + try + { + using (XmlDictionaryReader jsonReader = + jsonStream != null + ? JsonReaderWriterFactory.CreateJsonReader(jsonStream, XmlDictionaryReaderQuotas.Max) + : JsonReaderWriterFactory.CreateJsonReader(jsonBytes, XmlDictionaryReaderQuotas.Max)) + { + return JXMLToJsonValue(jsonReader); + } + } + catch (XmlException) + { + throw new FormatException(Properties.Resources.IncorrectJsonFormat); + } + } + + private static void InsertJsonValue(Stack<JsonValue> jsonStack, ref JsonValue parent, ref string currentName, JsonValue jsonValue, bool isEmptyElement) + { + if (parent is JsonArray) + { + ((JsonArray)parent).Add(jsonValue); + } + else + { + if (currentName != null) + { + ((JsonObject)parent)[currentName] = jsonValue; + currentName = null; + } + } + + if (!isEmptyElement) + { + jsonStack.Push(jsonValue); + parent = jsonValue; + } + } + + private static string GetMemberName(XmlDictionaryReader jsonReader) + { + string name; + if (jsonReader.NamespaceURI == ItemElementName && jsonReader.LocalName == ItemElementName) + { + // JXML special case for names which aren't valid XML names + name = jsonReader.GetAttribute(ItemElementName); + + if (name == null) + { + throw new FormatException(Properties.Resources.IncorrectJsonFormat); + } + } + else + { + name = jsonReader.Name; + } + + return name; + } + + private static JsonObject CreateObjectWithTypeHint(XmlDictionaryReader jsonReader, ref bool isEmptyElement) + { + JsonObject jsonObject = new JsonObject(); + string typeHintAttribute = jsonReader.GetAttribute(TypeHintAttributeName); + isEmptyElement = jsonReader.IsEmptyElement; + jsonReader.ReadStartElement(); + SkipWhitespace(jsonReader); + + if (typeHintAttribute != null) + { + jsonObject.Add(TypeHintAttributeName, typeHintAttribute); + } + + return jsonObject; + } + + private static JsonArray CreateJsonArray(XmlDictionaryReader jsonReader, ref bool isEmptyElement) + { + JsonArray jsonArray = new JsonArray(); + isEmptyElement = jsonReader.IsEmptyElement; + jsonReader.ReadStartElement(); + SkipWhitespace(jsonReader); + return jsonArray; + } + + private static void MoveToRootNode(XmlDictionaryReader jsonReader) + { + while (!jsonReader.EOF && (jsonReader.NodeType == XmlNodeType.None || jsonReader.NodeType == XmlNodeType.XmlDeclaration)) + { + // read into <root> node + jsonReader.Read(); + SkipWhitespace(jsonReader); + } + + if (jsonReader.NodeType != XmlNodeType.Element || !String.IsNullOrEmpty(jsonReader.NamespaceURI) || jsonReader.Name != RootElementName) + { + throw new FormatException(Properties.Resources.IncorrectJsonFormat); + } + } + + private static JsonPrimitive ReadPrimitive(string type, XmlDictionaryReader jsonReader) + { + JsonValue result = null; + switch (type) + { + case NullAttributeValue: + jsonReader.Skip(); + result = null; + break; + case BooleanAttributeValue: + result = jsonReader.ReadElementContentAsBoolean(); + break; + case StringAttributeValue: + result = jsonReader.ReadElementContentAsString(); + break; + case NumberAttributeValue: + string temp = jsonReader.ReadElementContentAsString(); + result = ConvertStringToJsonNumber(temp); + break; + } + + SkipWhitespace(jsonReader); + return (JsonPrimitive)result; + } + + private static void SkipWhitespace(XmlDictionaryReader reader) + { + while (!reader.EOF && (reader.NodeType == XmlNodeType.Whitespace || reader.NodeType == XmlNodeType.SignificantWhitespace)) + { + reader.Read(); + } + } + + private static JsonValue ConvertStringToJsonNumber(string value) + { + if (value.IndexOfAny(_floatingPointChars) < 0) + { + int intVal; + if (Int32.TryParse(value, NumberStyles.Float, CultureInfo.InvariantCulture, out intVal)) + { + return intVal; + } + + long longVal; + if (Int64.TryParse(value, NumberStyles.Float, CultureInfo.InvariantCulture, out longVal)) + { + return longVal; + } + } + + decimal decValue; + if (Decimal.TryParse(value, NumberStyles.Float, CultureInfo.InvariantCulture, out decValue) && decValue != 0) + { + return decValue; + } + + double dblValue; + if (Double.TryParse(value, NumberStyles.Float, CultureInfo.InvariantCulture, out dblValue)) + { + return dblValue; + } + + throw new ArgumentException(RS.Format(Properties.Resources.InvalidJsonPrimitive, value.ToString()), "value"); + } + } +} diff --git a/mcs/class/System.Json/System.Json/JsonArray.cs b/mcs/class/System.Json/System.Json/JsonArray.cs new file mode 100644 index 00000000000..21d0d03e5ca --- /dev/null +++ b/mcs/class/System.Json/System.Json/JsonArray.cs @@ -0,0 +1,389 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. + +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; +using System.Runtime.Serialization; +using System.Xml; + +namespace System.Json +{ + /// <summary> + /// A JsonArray is an ordered sequence of zero or more <see cref="System.Json.JsonValue"/> objects. + /// </summary> + [SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix", + Justification = "Array already conveys the meaning of collection")] + [DataContract] + public sealed class JsonArray : JsonValue, IList<JsonValue> + { + [DataMember] + private List<JsonValue> values = new List<JsonValue>(); + + /// <summary> + /// Creates an instance of the <see cref="System.Json.JsonArray"/> class initialized by + /// an <see cref="System.Collections.Generic.IEnumerable{T}"/> enumeration of + /// objects of type <see cref="System.Json.JsonValue"/>. + /// </summary> + /// <param name="items">The <see cref="System.Collections.Generic.IEnumerable{T}"/> enumeration + /// of objects of type <see cref="System.Json.JsonValue"/> used to initialize the JavaScript Object Notation (JSON) + /// array.</param> + /// <exception cref="System.ArgumentNullException">If items is null.</exception> + /// <exception cref="System.ArgumentException">If any of the items in the collection + /// is a <see cref="System.Json.JsonValue"/> with <see cref="System.Json.JsonValue.JsonType"/> property of + /// value <see cref="F:System.Json.JsonType.Default"/>.</exception> + public JsonArray(IEnumerable<JsonValue> items) + { + AddRange(items); + } + + /// <summary> + /// Creates an instance of the <see cref="System.Json.JsonArray"/> class, initialized by an array of type <see cref="System.Json.JsonValue"/>. + /// </summary> + /// <param name="items">The array of type <see cref="System.Json.JsonValue"/> used to initialize the + /// JavaScript Object Notation (JSON) array.</param> + /// <exception cref="System.ArgumentException">If any of the items in the collection + /// is a <see cref="System.Json.JsonValue"/> with <see cref="System.Json.JsonValue.JsonType"/> property of + /// value <see cref="F:System.Json.JsonType.Default"/>.</exception> + public JsonArray(params JsonValue[] items) + { + if (items != null) + { + AddRange(items); + } + } + + /// <summary> + /// Gets the JSON type of this <see cref="System.Json.JsonArray"/>. The return value + /// is always <see cref="F:System.Json.JsonType.Array"/>. + /// </summary> + public override JsonType JsonType + { + get { return JsonType.Array; } + } + + /// <summary> + /// Gets a value indicating whether the <see cref="System.Json.JsonArray"/> is read-only. + /// </summary> + public bool IsReadOnly + { + get { return ((IList)values).IsReadOnly; } + } + + /// <summary> + /// Returns the number of <see cref="System.Json.JsonValue"/> elements in the array. + /// </summary> + public override int Count + { + get { return values.Count; } + } + + /// <summary> + /// Gets or sets the JSON value at a specified index. + /// </summary> + /// <param name="index">The zero-based index of the element to get or set.</param> + /// <returns>The <see cref="System.Json.JsonValue"/> element at the specified index.</returns> + /// <exception cref="System.ArgumentOutOfRangeException">If index is not a valid index for this array.</exception> + /// <exception cref="System.ArgumentException">The property is set and the value is a + /// <see cref="System.Json.JsonValue"/> with <see cref="System.Json.JsonValue.JsonType"/> + /// property of value <see cref="F:System.Json.JsonType.Default"/>.</exception> + public override JsonValue this[int index] + { + get { return values[index]; } + + set + { + if (value != null && value.JsonType == JsonType.Default) + { + throw new ArgumentNullException("value", Properties.Resources.UseOfDefaultNotAllowed); + } + + JsonValue oldValue = values[index]; + RaiseItemChanging(value, JsonValueChange.Replace, index); + values[index] = value; + RaiseItemChanged(oldValue, JsonValueChange.Replace, index); + } + } + + /// <summary> + /// Adds the elements from a collection of type <see cref="System.Json.JsonValue"/> to this instance. + /// </summary> + /// <param name="items">Collection of items to add.</param> + /// <exception cref="System.ArgumentNullException">If items is null.</exception> + /// <exception cref="System.ArgumentException">If any of the items in the collection + /// is a <see cref="System.Json.JsonValue"/> with <see cref="System.Json.JsonValue.JsonType"/> property of + /// value <see cref="F:System.Json.JsonType.Default"/>.</exception> + public void AddRange(IEnumerable<JsonValue> items) + { + if (items == null) + { + throw new ArgumentNullException("items"); + } + + if (ChangingListenersCount > 0) + { + int index = Count; + foreach (JsonValue toBeAdded in items) + { + RaiseItemChanging(toBeAdded, JsonValueChange.Add, index++); + } + } + + foreach (JsonValue item in items) + { + if (item != null && item.JsonType == JsonType.Default) + { + throw new ArgumentNullException("items", Properties.Resources.UseOfDefaultNotAllowed); + } + + values.Add(item); + RaiseItemChanged(item, JsonValueChange.Add, values.Count - 1); + } + } + + /// <summary> + /// Adds the elements from an array of type <see cref="System.Json.JsonValue"/> to this instance. + /// </summary> + /// <param name="items">The array of type JsonValue to be added to this instance.</param> + /// <exception cref="System.ArgumentNullException">If items is null.</exception> + /// <exception cref="System.ArgumentException">If any of the items in the array + /// is a <see cref="System.Json.JsonValue"/> with <see cref="System.Json.JsonValue.JsonType"/> property of + /// value <see cref="F:System.Json.JsonType.Default"/>.</exception> + public void AddRange(params JsonValue[] items) + { + AddRange(items as IEnumerable<JsonValue>); + } + + /// <summary> + /// Searches for a specified object and returns the zero-based index of its first + /// occurrence within this <see cref="System.Json.JsonArray"/>. + /// </summary> + /// <param name="item">The <see cref="System.Json.JsonValue"/> object to look up.</param> + /// <returns>The zero-based index of the first occurrence of item within the + /// <see cref="System.Json.JsonArray"/>, if found; otherwise, -1.</returns> + public int IndexOf(JsonValue item) + { + return values.IndexOf(item); + } + + /// <summary> + /// Insert a JSON CLR type into the array at a specified index. + /// </summary> + /// <param name="index">The zero-based index at which the item should be inserted.</param> + /// <param name="item">The <see cref="System.Json.JsonValue"/> object to insert.</param> + /// <exception cref="System.ArgumentOutOfRangeException">If index is less than zero or larger than + /// the size of the array.</exception> + /// <exception cref="System.ArgumentException">If the object to insert has a + /// <see cref="System.Json.JsonValue.JsonType"/> property of value + /// <see cref="F:System.Json.JsonType.Default"/>.</exception> + public void Insert(int index, JsonValue item) + { + if (item != null && item.JsonType == JsonType.Default) + { + throw new ArgumentNullException("item", Properties.Resources.UseOfDefaultNotAllowed); + } + + RaiseItemChanging(item, JsonValueChange.Add, index); + values.Insert(index, item); + RaiseItemChanged(item, JsonValueChange.Add, index); + } + + /// <summary> + /// Remove the JSON value at a specified index of <see cref="System.Json.JsonArray"/>. + /// </summary> + /// <param name="index">The zero-based index at which to remove the <see cref="System.Json.JsonValue"/>.</param> + /// <exception cref="System.ArgumentOutOfRangeException">If index is less than zero or index + /// is equal or larger than the size of the array.</exception> + public void RemoveAt(int index) + { + JsonValue item = values[index]; + RaiseItemChanging(item, JsonValueChange.Remove, index); + values.RemoveAt(index); + RaiseItemChanged(item, JsonValueChange.Remove, index); + } + + /// <summary> + /// Adds a <see cref="System.Json.JsonValue"/> object to the end of the array. + /// </summary> + /// <param name="item">The <see cref="System.Json.JsonValue"/> object to add.</param> + /// <exception cref="System.ArgumentException">If the object to add has a + /// <see cref="System.Json.JsonValue.JsonType"/> property of value + /// <see cref="F:System.Json.JsonType.Default"/>.</exception> + public void Add(JsonValue item) + { + if (item != null && item.JsonType == JsonType.Default) + { + throw new ArgumentNullException("item", Properties.Resources.UseOfDefaultNotAllowed); + } + + int index = Count; + RaiseItemChanging(item, JsonValueChange.Add, index); + values.Add(item); + RaiseItemChanged(item, JsonValueChange.Add, index); + } + + /// <summary> + /// Removes all JSON CLR types from the <see cref="System.Json.JsonArray"/>. + /// </summary> + public void Clear() + { + RaiseItemChanging(null, JsonValueChange.Clear, 0); + values.Clear(); + RaiseItemChanged(null, JsonValueChange.Clear, 0); + } + + /// <summary> + /// Checks whether a specified JSON CLR type is in the <see cref="System.Json.JsonArray"/>. + /// </summary> + /// <param name="item">The <see cref="System.Json.JsonValue"/> to check for in the array.</param> + /// <returns>true if item is found in the <see cref="System.Json.JsonArray"/>; otherwise, false.</returns> + public bool Contains(JsonValue item) + { + return values.Contains(item); + } + + /// <summary> + /// Copies the contents of the current JSON CLR array instance into a specified + /// destination array beginning at the specified index. + /// </summary> + /// <param name="array">The destination array to which the elements of the current + /// <see cref="System.Json.JsonArray"/> object are copied.</param> + /// <param name="arrayIndex">The zero-based index in the destination array at which the + /// copying of the elements of the JSON CLR array begins.</param> + public void CopyTo(JsonValue[] array, int arrayIndex) + { + values.CopyTo(array, arrayIndex); + } + + /// <summary> + /// Removes the first occurrence of the specified JSON value from the array. + /// </summary> + /// <param name="item">The <see cref="System.Json.JsonValue"/> to remove from the <see cref="System.Json.JsonArray"/>.</param> + /// <returns>true if item is successfully removed; otherwise, false. This method + /// also returns false if item was not found in the <see cref="System.Json.JsonArray"/>.</returns> + public bool Remove(JsonValue item) + { + int index = -1; + if (ChangingListenersCount > 0 || ChangedListenersCount > 0) + { + index = IndexOf(item); + } + + if (index >= 0) + { + RaiseItemChanging(item, JsonValueChange.Remove, index); + } + + bool result = values.Remove(item); + if (index >= 0) + { + RaiseItemChanged(item, JsonValueChange.Remove, index); + } + + return result; + } + + /// <summary> + /// Returns an enumerator that iterates through the <see cref="System.Json.JsonValue"/> objects in the array. + /// </summary> + /// <returns>Returns an <see cref="System.Collections.IEnumerator"/> object that + /// iterates through the <see cref="System.Json.JsonValue"/> elements in this <see cref="System.Json.JsonArray"/>.</returns> + IEnumerator IEnumerable.GetEnumerator() + { + return values.GetEnumerator(); + } + + /// <summary> + /// Safe indexer for the <see cref="System.Json.JsonValue"/> type. + /// </summary> + /// <param name="index">The zero-based index of the element to get.</param> + /// <returns>If the index is within the array bounds and the value corresponding to the + /// index is not null, then it will return that value. Otherwise it will return a + /// <see cref="System.Json.JsonValue"/> instance with <see cref="System.Json.JsonValue.JsonType"/> + /// equals to <see cref="F:System.Json.JsonType.Default"/>.</returns> + public override JsonValue ValueOrDefault(int index) + { + if (index >= 0 && index < Count && this[index] != null) + { + return this[index]; + } + + return base.ValueOrDefault(index); + } + + /// <summary> + /// Returns an enumerator that iterates through the <see cref="System.Json.JsonValue"/> objects in the array. + /// </summary> + /// <returns>Returns an <see cref="System.Collections.Generic.IEnumerator{T}"/> object that + /// iterates through the <see cref="System.Json.JsonValue"/> elements in this <see cref="System.Json.JsonArray"/>.</returns> + public new IEnumerator<JsonValue> GetEnumerator() + { + return values.GetEnumerator(); + } + + /// <summary> + /// Returns an enumerator which iterates through the values in this object. + /// </summary> + /// <returns>An <see cref="System.Collections.Generic.IEnumerator{T}"/> which iterates through the values in this object.</returns> + /// <remarks>The enumerator returned by this class contains one pair for each element + /// in this array, whose key is the element index (as a string), and the value is the + /// element itself.</remarks> + protected override IEnumerator<KeyValuePair<string, JsonValue>> GetKeyValuePairEnumerator() + { + for (int i = 0; i < values.Count; i++) + { + yield return new KeyValuePair<string, JsonValue>(i.ToString(CultureInfo.InvariantCulture), values[i]); + } + } + + /// <summary> + /// Callback method called to let an instance write the proper JXML attribute when saving this + /// instance. + /// </summary> + /// <param name="jsonWriter">The JXML writer used to write JSON.</param> + internal override void WriteAttributeString(XmlDictionaryWriter jsonWriter) + { + if (jsonWriter == null) + { + throw new ArgumentNullException("jsonWriter"); + } + + jsonWriter.WriteAttributeString(JXmlToJsonValueConverter.TypeAttributeName, JXmlToJsonValueConverter.ArrayAttributeValue); + } + + /// <summary> + /// Callback method called during Save operations to let the instance write the start element + /// and return the next element in the collection. + /// </summary> + /// <param name="jsonWriter">The JXML writer used to write JSON.</param> + /// <param name="currentIndex">The index within this collection.</param> + /// <returns>The next item in the collection, or null of there are no more items.</returns> + internal override JsonValue WriteStartElementAndGetNext(XmlDictionaryWriter jsonWriter, int currentIndex) + { + if (jsonWriter == null) + { + throw new ArgumentNullException("jsonWriter"); + } + + jsonWriter.WriteStartElement(JXmlToJsonValueConverter.ItemElementName); + JsonValue nextValue = this[currentIndex]; + return nextValue; + } + + private void RaiseItemChanging(JsonValue child, JsonValueChange change, int index) + { + if (ChangingListenersCount > 0) + { + RaiseChangingEvent(this, new JsonValueChangeEventArgs(child, change, index)); + } + } + + private void RaiseItemChanged(JsonValue child, JsonValueChange change, int index) + { + if (ChangedListenersCount > 0) + { + RaiseChangedEvent(this, new JsonValueChangeEventArgs(child, change, index)); + } + } + } +} diff --git a/mcs/class/System.Json/System.Json/JsonObject.cs b/mcs/class/System.Json/System.Json/JsonObject.cs new file mode 100644 index 00000000000..a8831c64648 --- /dev/null +++ b/mcs/class/System.Json/System.Json/JsonObject.cs @@ -0,0 +1,474 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. + +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.Serialization; +using System.Xml; +using WrappedPair = System.Json.NGenWrapper<System.Collections.Generic.KeyValuePair<string, System.Json.JsonValue>>; + +namespace System.Json +{ + /// <summary> + /// A JsonObject is an unordered collection of zero or more key/value pairs. + /// </summary> + /// <remarks>A JsonObject is an unordered collection of zero or more key/value pairs, + /// where each key is a String and each value is a <see cref="System.Json.JsonValue"/>, which can be a + /// <see cref="System.Json.JsonPrimitive"/>, a <see cref="System.Json.JsonArray"/>, or a JsonObject.</remarks> + [SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix", + Justification = "Object in the context of JSON already conveys the meaning of dictionary")] + [DataContract] + public sealed class JsonObject : JsonValue, IDictionary<string, JsonValue> + { + [DataMember] + private Dictionary<string, JsonValue> values = new Dictionary<string, JsonValue>(StringComparer.Ordinal); + + private List<WrappedPair> indexedPairs; + private int instanceSaveCount; + private object saveLock = new object(); + + /// <summary> + /// Creates an instance of the <see cref="System.Json.JsonObject"/> class initialized with an + /// <see cref="System.Collections.Generic.IEnumerable{T}"/> collection of key/value pairs. + /// </summary> + /// <param name="items">The <see cref="System.Collections.Generic.IEnumerable{T}"/> collection of + /// <see cref="System.Collections.Generic.KeyValuePair{K, V}"/> used to initialize the + /// key/value pairs</param> + /// <exception cref="System.ArgumentNullException">If items is null.</exception> + /// <exception cref="System.ArgumentException">If any of the values in the collection + /// is a <see cref="System.Json.JsonValue"/> with <see cref="System.Json.JsonValue.JsonType"/> property of + /// value <see cref="F:System.Json.JsonType.Default"/>.</exception> + [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", + Justification = "There's no complexity using this design because nested generic type is atomic type not another collection")] + public JsonObject(IEnumerable<KeyValuePair<string, JsonValue>> items) + { + AddRange(items); + } + + /// <summary> + /// Creates an instance of the <see cref="System.Json.JsonObject"/> class initialized with a collection of key/value pairs. + /// </summary> + /// <param name="items">The <see cref="System.Collections.Generic.KeyValuePair{K, V}"/> objects used to initialize the key/value pairs.</param> + /// <exception cref="System.ArgumentException">If any of the values in the collection + /// is a <see cref="System.Json.JsonValue"/> with <see cref="System.Json.JsonValue.JsonType"/> property of + /// value <see cref="F:System.Json.JsonType.Default"/>.</exception> + public JsonObject(params KeyValuePair<string, JsonValue>[] items) + { + if (items != null) + { + AddRange(items); + } + } + + /// <summary> + /// Gets the JSON type of this <see cref="System.Json.JsonObject"/>. The return value + /// is always <see cref="F:System.Json.JsonType.Object"/>. + /// </summary> + public override JsonType JsonType + { + get { return JsonType.Object; } + } + + /// <summary> + /// Gets a collection that contains the keys in this <see cref="System.Json.JsonObject"/>. + /// </summary> + public ICollection<string> Keys + { + get { return values.Keys; } + } + + /// <summary> + /// Gets a collection that contains the values in this <see cref="System.Json.JsonObject"/>. + /// </summary> + public ICollection<JsonValue> Values + { + get { return values.Values; } + } + + /// <summary> + /// Returns the number of key/value pairs in this <see cref="System.Json.JsonObject"/>. + /// </summary> + public override int Count + { + get { return values.Count; } + } + + /// <summary> + /// Gets a value indicating whether this JSON CLR object is read-only. + /// </summary> + bool ICollection<KeyValuePair<string, JsonValue>>.IsReadOnly + { + get { return ((ICollection<KeyValuePair<string, JsonValue>>)values).IsReadOnly; } + } + + /// <summary> + /// Gets or sets the value associated with the specified key. + /// </summary> + /// <param name="key">The key of the value to get or set.</param> + /// <returns>The <see cref="System.Json.JsonValue"/> associated to the specified key.</returns> + /// <exception cref="System.ArgumentNullException">If key is null.</exception> + /// <exception cref="System.ArgumentException">The property is set and the value is a + /// <see cref="System.Json.JsonValue"/> with <see cref="System.Json.JsonValue.JsonType"/> + /// property of value <see cref="F:System.Json.JsonType.Default"/>.</exception> + public override JsonValue this[string key] + { + get + { + if (key == null) + { + throw new ArgumentNullException("key"); + } + + return values[key]; + } + + set + { + if (value != null && value.JsonType == JsonType.Default) + { + throw new ArgumentNullException("value", Properties.Resources.UseOfDefaultNotAllowed); + } + + if (key == null) + { + throw new ArgumentNullException("key"); + } + + bool replacement = values.ContainsKey(key); + JsonValue oldValue = null; + if (replacement) + { + oldValue = values[key]; + RaiseItemChanging(value, JsonValueChange.Replace, key); + } + else + { + RaiseItemChanging(value, JsonValueChange.Add, key); + } + + values[key] = value; + if (replacement) + { + RaiseItemChanged(oldValue, JsonValueChange.Replace, key); + } + else + { + RaiseItemChanged(value, JsonValueChange.Add, key); + } + } + } + + /// <summary> + /// Safe string indexer for the <see cref="System.Json.JsonValue"/> type. + /// </summary> + /// <param name="key">The key of the element to get.</param> + /// <returns>If this instance contains the given key and the value corresponding to + /// the key is not null, then it will return that value. Otherwise it will return a + /// <see cref="System.Json.JsonValue"/> instance with <see cref="System.Json.JsonValue.JsonType"/> + /// equals to <see cref="F:System.Json.JsonType.Default"/>.</returns> + public override JsonValue ValueOrDefault(string key) + { + if (key != null && ContainsKey(key) && this[key] != null) + { + return this[key]; + } + + return base.ValueOrDefault(key); + } + + /// <summary> + /// Adds a specified collection of key/value pairs to this instance. + /// </summary> + /// <param name="items">The collection of key/value pairs to add.</param> + /// <exception cref="System.ArgumentNullException">If items is null.</exception> + /// <exception cref="System.ArgumentException">If the value of any of the items in the collection + /// is a <see cref="System.Json.JsonValue"/> with <see cref="System.Json.JsonValue.JsonType"/> property of + /// value <see cref="F:System.Json.JsonType.Default"/>.</exception> + [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", + Justification = "There's no complexity using this design because nested generic type is atomic type not another collection")] + public void AddRange(IEnumerable<KeyValuePair<string, JsonValue>> items) + { + if (items == null) + { + throw new ArgumentNullException("items"); + } + + if (ChangingListenersCount > 0) + { + foreach (KeyValuePair<string, JsonValue> item in items) + { + RaiseItemChanging(item.Value, JsonValueChange.Add, item.Key); + } + } + + foreach (KeyValuePair<string, JsonValue> item in items) + { + if (item.Value != null && item.Value.JsonType == JsonType.Default) + { + throw new ArgumentNullException("items", Properties.Resources.UseOfDefaultNotAllowed); + } + + values.Add(item.Key, item.Value); + RaiseItemChanged(item.Value, JsonValueChange.Add, item.Key); + } + } + + /// <summary> + /// Adds the elements from an array of type <see cref="System.Json.JsonValue"/> to this instance. + /// </summary> + /// <param name="items">The array of key/value paris to be added to this instance.</param> + /// <exception cref="System.ArgumentException">If the value of any of the items in the array + /// is a <see cref="System.Json.JsonValue"/> with <see cref="System.Json.JsonValue.JsonType"/> property of + /// value <see cref="F:System.Json.JsonType.Default"/>.</exception> + public void AddRange(params KeyValuePair<string, JsonValue>[] items) + { + AddRange(items as IEnumerable<KeyValuePair<string, JsonValue>>); + } + + /// <summary> + /// + /// </summary> + /// <returns></returns> + IEnumerator IEnumerable.GetEnumerator() + { + return ((IEnumerable)values).GetEnumerator(); + } + + /// <summary> + /// Adds a key/value pair to this <see cref="System.Json.JsonObject"/> instance. + /// </summary> + /// <param name="key">The key for the element added.</param> + /// <param name="value">The <see cref="System.Json.JsonValue"/> for the element added.</param> + /// <exception cref="System.ArgumentException">If the value is a <see cref="System.Json.JsonValue"/> + /// with <see cref="System.Json.JsonValue.JsonType"/> property of + /// value <see cref="F:System.Json.JsonType.Default"/>.</exception> + public void Add(string key, JsonValue value) + { + if (value != null && value.JsonType == JsonType.Default) + { + throw new ArgumentNullException("value", Properties.Resources.UseOfDefaultNotAllowed); + } + + RaiseItemChanging(value, JsonValueChange.Add, key); + values.Add(key, value); + RaiseItemChanged(value, JsonValueChange.Add, key); + } + + /// <summary> + /// Adds a key/value pair to this <see cref="System.Json.JsonObject"/> instance. + /// </summary> + /// <param name="item">The key/value pair to be added.</param> + /// <exception cref="System.ArgumentException">If the value of the pair is a + /// <see cref="System.Json.JsonValue"/> with <see cref="System.Json.JsonValue.JsonType"/> + /// property of value <see cref="F:System.Json.JsonType.Default"/>.</exception> + public void Add(KeyValuePair<string, JsonValue> item) + { + Add(item.Key, item.Value); + } + + /// <summary> + /// Checks whether a key/value pair with a specified key exists in this <see cref="System.Json.JsonObject"/> instance. + /// </summary> + /// <param name="key">The key to check for.</param> + /// <returns>true if this instance contains the key; otherwise, false.</returns> + public override bool ContainsKey(string key) + { + return values.ContainsKey(key); + } + + /// <summary> + /// Removes the key/value pair with a specified key from this <see cref="System.Json.JsonObject"/> instance. + /// </summary> + /// <param name="key">The key of the item to remove.</param> + /// <returns>true if the element is successfully found and removed; otherwise, false. + /// This method returns false if key is not found in this <see cref="System.Json.JsonObject"/> instance.</returns> + public bool Remove(string key) + { + JsonValue original = null; + bool containsKey = false; + if (ChangingListenersCount > 0 || ChangedListenersCount > 0) + { + containsKey = TryGetValue(key, out original); + } + + if (containsKey && ChangingListenersCount > 0) + { + RaiseItemChanging(original, JsonValueChange.Remove, key); + } + + bool result = values.Remove(key); + + if (containsKey && ChangedListenersCount > 0) + { + RaiseItemChanged(original, JsonValueChange.Remove, key); + } + + return result; + } + + /// <summary> + /// Attempts to get the value that corresponds to the specified key. + /// </summary> + /// <param name="key">The key of the value to retrieve.</param> + /// <param name="value">The primitive or structured <see cref="System.Json.JsonValue"/> object that has the key + /// specified. If this object does not contain a key/value pair with the given key, + /// this parameter is set to null.</param> + /// <returns>true if the instance of the <see cref="System.Json.JsonObject"/> contains an element with the + /// specified key; otherwise, false.</returns> + public bool TryGetValue(string key, out JsonValue value) + { + return values.TryGetValue(key, out value); + } + + /// <summary> + /// Removes all key/value pairs from this <see cref="System.Json.JsonObject"/> instance. + /// </summary> + public void Clear() + { + RaiseItemChanging(null, JsonValueChange.Clear, null); + values.Clear(); + RaiseItemChanged(null, JsonValueChange.Clear, null); + } + + bool ICollection<KeyValuePair<string, JsonValue>>.Contains(KeyValuePair<string, JsonValue> item) + { + return ((ICollection<KeyValuePair<string, JsonValue>>)values).Contains(item); + } + + /// <summary> + /// Copies the contents of this <see cref="System.Json.JsonObject"/> instance into a specified + /// key/value destination array beginning at a specified index. + /// </summary> + /// <param name="array">The destination array of type <see cref="System.Collections.Generic.KeyValuePair{K, V}"/> + /// to which the elements of this <see cref="System.Json.JsonObject"/> are copied.</param> + /// <param name="arrayIndex">The zero-based index at which to begin the insertion of the + /// contents from this <see cref="System.Json.JsonObject"/> instance.</param> + public void CopyTo(KeyValuePair<string, JsonValue>[] array, int arrayIndex) + { + ((ICollection<KeyValuePair<string, JsonValue>>)values).CopyTo(array, arrayIndex); + } + + bool ICollection<KeyValuePair<string, JsonValue>>.Remove(KeyValuePair<string, JsonValue> item) + { + if (ChangingListenersCount > 0) + { + if (ContainsKey(item.Key) && EqualityComparer<JsonValue>.Default.Equals(item.Value, values[item.Key])) + { + RaiseItemChanging(item.Value, JsonValueChange.Remove, item.Key); + } + } + + bool result = ((ICollection<KeyValuePair<string, JsonValue>>)values).Remove(item); + if (result) + { + RaiseItemChanged(item.Value, JsonValueChange.Remove, item.Key); + } + + return result; + } + + /// <summary> + /// Returns an enumerator over the key/value pairs contained in this <see cref="System.Json.JsonObject"/> instance. + /// </summary> + /// <returns>An <see cref="System.Collections.Generic.IEnumerator{T}"/> which iterates + /// through the members of this instance.</returns> + protected override IEnumerator<KeyValuePair<string, JsonValue>> GetKeyValuePairEnumerator() + { + return values.GetEnumerator(); + } + + /// <summary> + /// Callback method called when a Save operation is starting for this instance. + /// </summary> + protected override void OnSaveStarted() + { + lock (saveLock) + { + instanceSaveCount++; + if (indexedPairs == null) + { + indexedPairs = new List<WrappedPair>(); + + foreach (KeyValuePair<string, JsonValue> item in values) + { + indexedPairs.Add(new WrappedPair(item)); + } + } + } + } + + /// <summary> + /// Callback method called when a Save operation is finished for this instance. + /// </summary> + protected override void OnSaveEnded() + { + lock (saveLock) + { + instanceSaveCount--; + if (instanceSaveCount == 0) + { + indexedPairs = null; + } + } + } + + /// <summary> + /// Callback method called to let an instance write the proper JXML attribute when saving this + /// instance. + /// </summary> + /// <param name="jsonWriter">The JXML writer used to write JSON.</param> + internal override void WriteAttributeString(XmlDictionaryWriter jsonWriter) + { + jsonWriter.WriteAttributeString(JXmlToJsonValueConverter.TypeAttributeName, JXmlToJsonValueConverter.ObjectAttributeValue); + } + + /// <summary> + /// Callback method called during Save operations to let the instance write the start element + /// and return the next element in the collection. + /// </summary> + /// <param name="jsonWriter">The JXML writer used to write JSON.</param> + /// <param name="currentIndex">The index within this collection.</param> + /// <returns>The next item in the collection, or null of there are no more items.</returns> + internal override JsonValue WriteStartElementAndGetNext(XmlDictionaryWriter jsonWriter, int currentIndex) + { + KeyValuePair<string, JsonValue> currentPair = indexedPairs[currentIndex]; + string currentKey = currentPair.Key; + + if (currentKey.Length == 0) + { + // special case in JXML world + jsonWriter.WriteStartElement(JXmlToJsonValueConverter.ItemElementName, JXmlToJsonValueConverter.ItemElementName); + jsonWriter.WriteAttributeString(JXmlToJsonValueConverter.ItemElementName, String.Empty); + } + else + { + jsonWriter.WriteStartElement(currentKey); + } + + return currentPair.Value; + } + + [SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", Justification = "Context is required by CLR for this to work.")] + [OnDeserialized] + private void OnDeserialized(StreamingContext context) + { + saveLock = new object(); + } + + private void RaiseItemChanging(JsonValue child, JsonValueChange change, string key) + { + if (ChangingListenersCount > 0) + { + RaiseChangingEvent(this, new JsonValueChangeEventArgs(child, change, key)); + } + } + + private void RaiseItemChanged(JsonValue child, JsonValueChange change, string key) + { + if (ChangedListenersCount > 0) + { + RaiseChangedEvent(this, new JsonValueChangeEventArgs(child, change, key)); + } + } + } +} diff --git a/mcs/class/System.Json/System.Json/JsonPrimitive.cs b/mcs/class/System.Json/System.Json/JsonPrimitive.cs new file mode 100644 index 00000000000..6d5cc7c520f --- /dev/null +++ b/mcs/class/System.Json/System.Json/JsonPrimitive.cs @@ -0,0 +1,1151 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Diagnostics.Contracts; +using System.Globalization; +using System.Runtime.Serialization; +using System.Text; +using System.Xml; + +namespace System.Json +{ + /// <summary> + /// Represents a JavaScript Object Notation (JSON) primitive type in the common language runtime (CLR). + /// </summary> + [SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix", + Justification = "JsonPrimitive does not represent a collection.")] + [DataContract] + public sealed class JsonPrimitive : JsonValue + { + internal const string DateTimeIsoFormat = "yyyy-MM-ddTHH:mm:ss.fffK"; + private const string UtcString = "UTC"; + private const string GmtString = "GMT"; + private static readonly long UnixEpochTicks = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).Ticks; + private static readonly char[] FloatingPointChars = new char[] { '.', 'e', 'E' }; + private static readonly Type jsonPrimitiveType = typeof(JsonPrimitive); + private static readonly Type uriType = typeof(Uri); + + private static readonly Dictionary<Type, Func<string, ConvertResult>> stringConverters = new Dictionary<Type, Func<string, ConvertResult>> + { + { typeof(bool), new Func<string, ConvertResult>(StringToBool) }, + { typeof(byte), new Func<string, ConvertResult>(StringToByte) }, + { typeof(char), new Func<string, ConvertResult>(StringToChar) }, + { typeof(sbyte), new Func<string, ConvertResult>(StringToSByte) }, + { typeof(short), new Func<string, ConvertResult>(StringToShort) }, + { typeof(int), new Func<string, ConvertResult>(StringToInt) }, + { typeof(long), new Func<string, ConvertResult>(StringToLong) }, + { typeof(ushort), new Func<string, ConvertResult>(StringToUShort) }, + { typeof(uint), new Func<string, ConvertResult>(StringToUInt) }, + { typeof(ulong), new Func<string, ConvertResult>(StringToULong) }, + { typeof(float), new Func<string, ConvertResult>(StringToFloat) }, + { typeof(double), new Func<string, ConvertResult>(StringToDouble) }, + { typeof(decimal), new Func<string, ConvertResult>(StringToDecimal) }, + { typeof(DateTime), new Func<string, ConvertResult>(StringToDateTime) }, + { typeof(DateTimeOffset), new Func<string, ConvertResult>(StringToDateTimeOffset) }, + { typeof(Guid), new Func<string, ConvertResult>(StringToGuid) }, + { typeof(Uri), new Func<string, ConvertResult>(StringToUri) }, + }; + + [DataMember] + private object value; + + [DataMember] + private JsonType jsonType; + + /// <summary> + /// Initializes a new instance of a <see cref="System.Json.JsonPrimitive"/> type with a <see cref="System.Boolean"/> type. + /// </summary> + /// <param name="value">The <see cref="System.Boolean"/> object that initializes the new instance.</param> + /// <remarks>A <see cref="System.Json.JsonPrimitive"/> object stores a <see cref="System.Json.JsonType"/> and the value used to initialize it. + /// When initialized with a <see cref="System.Boolean"/> object, the <see cref="System.Json.JsonType"/> is a <see cref="F:System.Json.JsonType.Boolean"/>, which can be + /// recovered using the <see cref="System.Json.JsonPrimitive.JsonType"/> property. The value used to initialize the <see cref="System.Json.JsonPrimitive"/> + /// object can be recovered by casting the <see cref="System.Json.JsonPrimitive"/> to <see cref="System.Boolean"/>.</remarks> + public JsonPrimitive(bool value) + { + jsonType = JsonType.Boolean; + this.value = value; + } + + /// <summary> + /// Initializes a new instance of a <see cref="System.Json.JsonPrimitive"/> type with a <see cref="System.Byte"/> type. + /// </summary> + /// <param name="value">The <see cref="System.Byte"/> object that initializes the new instance.</param> + /// <remarks>A <see cref="System.Json.JsonPrimitive"/> object stores a <see cref="System.Json.JsonType"/> and the value used to initialize it. + /// When initialized with a <see cref="System.Byte"/> object, the <see cref="System.Json.JsonType"/> is a <see cref="F:System.Json.JsonType.Number"/>, which can be + /// recovered using the <see cref="System.Json.JsonPrimitive.JsonType"/> property. The value used to initialize the <see cref="System.Json.JsonPrimitive"/> + /// object can be recovered by casting the <see cref="System.Json.JsonPrimitive"/> to <see cref="System.Byte"/>.</remarks> + public JsonPrimitive(byte value) + { + jsonType = JsonType.Number; + this.value = value; + } + + /// <summary> + /// Initializes a new instance of a <see cref="System.Json.JsonPrimitive"/> type with a <see cref="System.SByte"/> type. + /// </summary> + /// <param name="value">The <see cref="System.SByte"/> object that initializes the new instance.</param> + /// <remarks>A <see cref="System.Json.JsonPrimitive"/> object stores a <see cref="System.Json.JsonType"/> and the value used to initialize it. + /// When initialized with a <see cref="System.SByte"/> object, the <see cref="System.Json.JsonType"/> is a <see cref="F:System.Json.JsonType.Number"/>, which can be + /// recovered using the <see cref="System.Json.JsonPrimitive.JsonType"/> property. The value used to initialize the <see cref="System.Json.JsonPrimitive"/> + /// object can be recovered by casting the <see cref="System.Json.JsonPrimitive"/> to <see cref="System.SByte"/>.</remarks> + [CLSCompliant(false)] + public JsonPrimitive(sbyte value) + { + jsonType = JsonType.Number; + this.value = value; + } + + /// <summary> + /// Initializes a new instance of a <see cref="System.Json.JsonPrimitive"/> type with a <see cref="System.Decimal"/> type. + /// </summary> + /// <param name="value">The <see cref="System.Decimal"/> object that initializes the new instance.</param> + /// <remarks>A <see cref="System.Json.JsonPrimitive"/> object stores a <see cref="System.Json.JsonType"/> and the value used to initialize it. + /// When initialized with a <see cref="System.Decimal"/> object, the <see cref="System.Json.JsonType"/> is a <see cref="F:System.Json.JsonType.Number"/>, which can be + /// recovered using the <see cref="System.Json.JsonPrimitive.JsonType"/> property. The value used to initialize the <see cref="System.Json.JsonPrimitive"/> + /// object can be recovered by casting the <see cref="System.Json.JsonPrimitive"/> to <see cref="System.Decimal"/>.</remarks> + public JsonPrimitive(decimal value) + { + jsonType = JsonType.Number; + this.value = value; + } + + /// <summary> + /// Initializes a new instance of a <see cref="System.Json.JsonPrimitive"/> type with a <see cref="System.Int16"/> type. + /// </summary> + /// <param name="value">The <see cref="System.Int16"/> object that initializes the new instance.</param> + /// <remarks>A <see cref="System.Json.JsonPrimitive"/> object stores a <see cref="System.Json.JsonType"/> and the value used to initialize it. + /// When initialized with a <see cref="System.Int16"/> object, the <see cref="System.Json.JsonType"/> is a <see cref="F:System.Json.JsonType.Number"/>, which can be + /// recovered using the <see cref="System.Json.JsonPrimitive.JsonType"/> property. The value used to initialize the <see cref="System.Json.JsonPrimitive"/> + /// object can be recovered by casting the <see cref="System.Json.JsonPrimitive"/> to <see cref="System.Int16"/>.</remarks> + public JsonPrimitive(short value) + { + jsonType = JsonType.Number; + this.value = value; + } + + /// <summary> + /// Initializes a new instance of a <see cref="System.Json.JsonPrimitive"/> type with a <see cref="System.UInt16"/> type. + /// </summary> + /// <param name="value">The <see cref="System.UInt16"/> object that initializes the new instance.</param> + /// <remarks>A <see cref="System.Json.JsonPrimitive"/> object stores a <see cref="System.Json.JsonType"/> and the value used to initialize it. + /// When initialized with a <see cref="System.UInt16"/> object, the <see cref="System.Json.JsonType"/> is a <see cref="F:System.Json.JsonType.Number"/>, which can be + /// recovered using the <see cref="System.Json.JsonPrimitive.JsonType"/> property. The value used to initialize the <see cref="System.Json.JsonPrimitive"/> + /// object can be recovered by casting the <see cref="System.Json.JsonPrimitive"/> to <see cref="System.UInt16"/>.</remarks> + [CLSCompliant(false)] + public JsonPrimitive(ushort value) + { + jsonType = JsonType.Number; + this.value = value; + } + + /// <summary> + /// Initializes a new instance of a <see cref="System.Json.JsonPrimitive"/> type with a <see cref="System.Int32"/> type. + /// </summary> + /// <param name="value">The <see cref="System.Int32"/> object that initializes the new instance.</param> + /// <remarks>A <see cref="System.Json.JsonPrimitive"/> object stores a <see cref="System.Json.JsonType"/> and the value used to initialize it. + /// When initialized with a <see cref="System.Int32"/> object, the <see cref="System.Json.JsonType"/> is a <see cref="F:System.Json.JsonType.Number"/>, which can be + /// recovered using the <see cref="System.Json.JsonPrimitive.JsonType"/> property. The value used to initialize the <see cref="System.Json.JsonPrimitive"/> + /// object can be recovered by casting the <see cref="System.Json.JsonPrimitive"/> to <see cref="System.Int32"/>.</remarks> + public JsonPrimitive(int value) + { + jsonType = JsonType.Number; + this.value = value; + } + + /// <summary> + /// Initializes a new instance of a <see cref="System.Json.JsonPrimitive"/> type with a <see cref="System.UInt32"/> type. + /// </summary> + /// <param name="value">The <see cref="System.UInt32"/> object that initializes the new instance.</param> + /// <remarks>A <see cref="System.Json.JsonPrimitive"/> object stores a <see cref="System.Json.JsonType"/> and the value used to initialize it. + /// When initialized with a <see cref="System.UInt32"/> object, the <see cref="System.Json.JsonType"/> is a <see cref="F:System.Json.JsonType.Number"/>, which can be + /// recovered using the <see cref="System.Json.JsonPrimitive.JsonType"/> property. The value used to initialize the <see cref="System.Json.JsonPrimitive"/> + /// object can be recovered by casting the <see cref="System.Json.JsonPrimitive"/> to <see cref="System.UInt32"/>.</remarks> + [CLSCompliant(false)] + public JsonPrimitive(uint value) + { + jsonType = JsonType.Number; + this.value = value; + } + + /// <summary> + /// Initializes a new instance of a <see cref="System.Json.JsonPrimitive"/> type with a <see cref="System.Int64"/> type. + /// </summary> + /// <param name="value">The <see cref="System.Int64"/> object that initializes the new instance.</param> + /// <remarks>A <see cref="System.Json.JsonPrimitive"/> object stores a <see cref="System.Json.JsonType"/> and the value used to initialize it. + /// When initialized with a <see cref="System.Int64"/> object, the <see cref="System.Json.JsonType"/> is a <see cref="F:System.Json.JsonType.Number"/>, which can be + /// recovered using the <see cref="System.Json.JsonPrimitive.JsonType"/> property. The value used to initialize the <see cref="System.Json.JsonPrimitive"/> + /// object can be recovered by casting the <see cref="System.Json.JsonPrimitive"/> to <see cref="System.Int64"/>.</remarks> + public JsonPrimitive(long value) + { + jsonType = JsonType.Number; + this.value = value; + } + + /// <summary> + /// Initializes a new instance of a <see cref="System.Json.JsonPrimitive"/> type with a <see cref="System.UInt64"/> type. + /// </summary> + /// <param name="value">The <see cref="System.UInt64"/> object that initializes the new instance.</param> + /// <remarks>A <see cref="System.Json.JsonPrimitive"/> object stores a <see cref="System.Json.JsonType"/> and the value used to initialize it. + /// When initialized with a <see cref="System.UInt64"/> object, the <see cref="System.Json.JsonType"/> is a <see cref="F:System.Json.JsonType.Number"/>, which can be + /// recovered using the <see cref="System.Json.JsonPrimitive.JsonType"/> property. The value used to initialize the <see cref="System.Json.JsonPrimitive"/> + /// object can be recovered by casting the <see cref="System.Json.JsonPrimitive"/> to <see cref="System.UInt64"/>.</remarks> + [CLSCompliant(false)] + public JsonPrimitive(ulong value) + { + jsonType = JsonType.Number; + this.value = value; + } + + /// <summary> + /// Initializes a new instance of a <see cref="System.Json.JsonPrimitive"/> type with a <see cref="System.Single"/> type. + /// </summary> + /// <param name="value">The <see cref="System.Single"/> object that initializes the new instance.</param> + /// <remarks>A <see cref="System.Json.JsonPrimitive"/> object stores a <see cref="System.Json.JsonType"/> and the value used to initialize it. + /// When initialized with a <see cref="System.Single"/> object, the <see cref="System.Json.JsonType"/> is a <see cref="F:System.Json.JsonType.Number"/>, which can be + /// recovered using the <see cref="System.Json.JsonPrimitive.JsonType"/> property. The value used to initialize the <see cref="System.Json.JsonPrimitive"/> + /// object can be recovered by casting the <see cref="System.Json.JsonPrimitive"/> to <see cref="System.Single"/>.</remarks> + public JsonPrimitive(float value) + { + jsonType = JsonType.Number; + this.value = value; + } + + /// <summary> + /// Initializes a new instance of a <see cref="System.Json.JsonPrimitive"/> type with a <see cref="System.Double"/> type. + /// </summary> + /// <param name="value">The <see cref="System.Double"/> object that initializes the new instance.</param> + /// <remarks>A <see cref="System.Json.JsonPrimitive"/> object stores a <see cref="System.Json.JsonType"/> and the value used to initialize it. + /// When initialized with a <see cref="System.Double"/> object, the <see cref="System.Json.JsonType"/> is a <see cref="F:System.Json.JsonType.Number"/>, which can be + /// recovered using the <see cref="System.Json.JsonPrimitive.JsonType"/> property. The value used to initialize the <see cref="System.Json.JsonPrimitive"/> + /// object can be recovered by casting the <see cref="System.Json.JsonPrimitive"/> to <see cref="System.Double"/>.</remarks> + public JsonPrimitive(double value) + { + jsonType = JsonType.Number; + this.value = value; + } + + /// <summary> + /// Initializes a new instance of a <see cref="System.Json.JsonPrimitive"/> type with a <see cref="System.String"/> type. + /// </summary> + /// <param name="value">The <see cref="System.String"/> object that initializes the new instance.</param> + /// <remarks>A <see cref="System.Json.JsonPrimitive"/> object stores a <see cref="System.Json.JsonType"/> and the value used to initialize it. + /// When initialized with a <see cref="System.String"/> object, the <see cref="System.Json.JsonType"/> is a <see cref="F:System.Json.JsonType.String"/>, which can be + /// recovered using the <see cref="System.Json.JsonPrimitive.JsonType"/> property. The value used to initialize the <see cref="System.Json.JsonPrimitive"/> + /// object can be recovered by casting the <see cref="System.Json.JsonPrimitive"/> to <see cref="System.String"/>.</remarks> + /// <exception cref="System.ArgumentNullException">value is null.</exception> + [SuppressMessage("Microsoft.Design", "CA1057:StringUriOverloadsCallSystemUriOverloads", + Justification = "This operator does not intend to represent a Uri overload.")] + public JsonPrimitive(string value) + { + if (value == null) + { + throw new ArgumentNullException("value"); + } + + jsonType = JsonType.String; + this.value = value; + } + + /// <summary> + /// Initializes a new instance of a <see cref="System.Json.JsonPrimitive"/> type with a <see cref="System.Char"/> type. + /// </summary> + /// <param name="value">The <see cref="System.Char"/> object that initializes the new instance.</param> + /// <remarks>A <see cref="System.Json.JsonPrimitive"/> object stores a <see cref="System.Json.JsonType"/> and the value used to initialize it. + /// When initialized with a <see cref="System.Char"/> object, the <see cref="System.Json.JsonType"/> is a <see cref="F:System.Json.JsonType.String"/>, which can be + /// recovered using the <see cref="System.Json.JsonPrimitive.JsonType"/> property. The value used to initialize the <see cref="System.Json.JsonPrimitive"/> + /// object can be recovered by casting the <see cref="System.Json.JsonPrimitive"/> to <see cref="System.Char"/>.</remarks> + public JsonPrimitive(char value) + { + jsonType = JsonType.String; + this.value = value; + } + + /// <summary> + /// Initializes a new instance of a <see cref="System.Json.JsonPrimitive"/> type with a <see cref="System.DateTime"/> type. + /// </summary> + /// <param name="value">The <see cref="System.DateTime"/> object that initializes the new instance.</param> + /// <remarks>A <see cref="System.Json.JsonPrimitive"/> object stores a <see cref="System.Json.JsonType"/> and the value used to initialize it. + /// When initialized with a <see cref="System.DateTime"/> object, the <see cref="System.Json.JsonType"/> is a <see cref="F:System.Json.JsonType.String"/>, which can be + /// recovered using the <see cref="System.Json.JsonPrimitive.JsonType"/> property. The value used to initialize the <see cref="System.Json.JsonPrimitive"/> + /// object can be recovered by casting the <see cref="System.Json.JsonPrimitive"/> to <see cref="System.DateTime"/>.</remarks> + public JsonPrimitive(DateTime value) + { + jsonType = JsonType.String; + this.value = value; + } + + /// <summary> + /// Initializes a new instance of a <see cref="System.Json.JsonPrimitive"/> type with a <see cref="System.DateTimeOffset"/> type. + /// </summary> + /// <param name="value">The <see cref="System.DateTimeOffset"/> object that initializes the new instance.</param> + /// <remarks>A <see cref="System.Json.JsonPrimitive"/> object stores a <see cref="System.Json.JsonType"/> and the value used to initialize it. + /// When initialized with a <see cref="System.DateTimeOffset"/> object, the <see cref="System.Json.JsonType"/> is a <see cref="F:System.Json.JsonType.String"/>, which can be + /// recovered using the <see cref="System.Json.JsonPrimitive.JsonType"/> property. The value used to initialize the <see cref="System.Json.JsonPrimitive"/> + /// object can be recovered by casting the <see cref="System.Json.JsonPrimitive"/> to <see cref="System.DateTimeOffset"/>.</remarks> + public JsonPrimitive(DateTimeOffset value) + { + jsonType = JsonType.String; + this.value = value; + } + + /// <summary> + /// Initializes a new instance of a <see cref="System.Json.JsonPrimitive"/> type with a <see cref="System.Uri"/> type. + /// </summary> + /// <param name="value">The <see cref="System.Uri"/> object that initializes the new instance.</param> + /// <remarks>A <see cref="System.Json.JsonPrimitive"/> object stores a <see cref="System.Json.JsonType"/> and the value used to initialize it. + /// When initialized with a <see cref="System.Uri"/> object, the <see cref="System.Json.JsonType"/> is a <see cref="F:System.Json.JsonType.String"/>, which can be + /// recovered using the <see cref="System.Json.JsonPrimitive.JsonType"/> property. The value used to initialize the <see cref="System.Json.JsonPrimitive"/> + /// object can be recovered by casting the <see cref="System.Json.JsonPrimitive"/> to <see cref="System.Uri"/>.</remarks> + /// <exception cref="System.ArgumentNullException">value is null.</exception> + public JsonPrimitive(Uri value) + { + if (value == null) + { + throw new ArgumentNullException("value"); + } + + jsonType = JsonType.String; + this.value = value; + } + + /// <summary> + /// Initializes a new instance of a <see cref="System.Json.JsonPrimitive"/> type with a <see cref="System.Guid"/> type. + /// </summary> + /// <param name="value">The <see cref="System.Guid"/> object that initializes the new instance.</param> + /// <remarks>A <see cref="System.Json.JsonPrimitive"/> object stores a <see cref="System.Json.JsonType"/> and the value used to initialize it. + /// When initialized with a <see cref="System.Guid"/> object, the <see cref="System.Json.JsonType"/> is a <see cref="F:System.Json.JsonType.String"/>, which can be + /// recovered using the <see cref="System.Json.JsonPrimitive.JsonType"/> property. The value used to initialize the <see cref="System.Json.JsonPrimitive"/> + /// object can be recovered by casting the <see cref="System.Json.JsonPrimitive"/> to <see cref="System.Guid"/>.</remarks> + public JsonPrimitive(Guid value) + { + jsonType = JsonType.String; + this.value = value; + } + + private JsonPrimitive(object value, JsonType type) + { + jsonType = type; + this.value = value; + } + + private enum ReadAsFailureKind + { + NoFailure, + InvalidCast, + InvalidDateFormat, + InvalidFormat, + InvalidUriFormat, + Overflow, + } + + /// <summary> + /// Gets the JsonType that is associated with this <see cref="System.Json.JsonPrimitive"/> object. + /// </summary> + public override JsonType JsonType + { + get { return jsonType; } + } + + /// <summary> + /// Gets the value represented by this instance. + /// </summary> + [SuppressMessage("Microsoft.Naming", "CA1721:PropertyNamesShouldNotMatchGetMethods", + Justification = "Value in this context clearly refers to the underlying CLR value")] + public object Value + { + get { return value; } + } + + /// <summary> + /// Attempts to create a <see cref="JsonPrimitive"/> instance from the specified <see cref="object"/> value. + /// </summary> + /// <param name="value">The <see cref="object"/> value to create the <see cref="JsonPrimitive"/> instance.</param> + /// <param name="result">The resulting <see cref="JsonPrimitive"/> instance on success, null otherwise.</param> + /// <returns>true if the operation is successful, false otherwise.</returns> + public static bool TryCreate(object value, out JsonPrimitive result) + { + bool allowedType = true; + JsonType jsonType = default(JsonType); + + if (value != null) + { + Type type = value.GetType(); + switch (Type.GetTypeCode(type)) + { + case TypeCode.Boolean: + jsonType = JsonType.Boolean; + break; + case TypeCode.Byte: + case TypeCode.SByte: + case TypeCode.Decimal: + case TypeCode.Double: + case TypeCode.Int16: + case TypeCode.Int32: + case TypeCode.Int64: + case TypeCode.UInt16: + case TypeCode.UInt32: + case TypeCode.UInt64: + case TypeCode.Single: + jsonType = JsonType.Number; + break; + case TypeCode.String: + case TypeCode.Char: + case TypeCode.DateTime: + jsonType = JsonType.String; + break; + default: + if (type == typeof(Uri) || type == typeof(Guid) || type == typeof(DateTimeOffset)) + { + jsonType = JsonType.String; + } + else + { + allowedType = false; + } + + break; + } + } + else + { + allowedType = false; + } + + if (allowedType) + { + result = new JsonPrimitive(value, jsonType); + return true; + } + else + { + result = null; + return false; + } + } + + /// <summary> + /// Attempts to convert this <see cref="System.Json.JsonPrimitive"/> instance into an instance of the specified type. + /// </summary> + /// <param name="type">The type to which the conversion is being performed.</param> + /// <returns>An object instance initialized with the <see cref="System.Json.JsonValue"/> value + /// specified if the conversion.</returns> + /// <exception cref="System.UriFormatException">If T is <see cref="System.Uri"/> and this value does + /// not represent a valid Uri.</exception> + /// <exception cref="OverflowException">If T is a numeric type, and a narrowing conversion would result + /// in a loss of data. For example, if this instance holds an <see cref="System.Int32"/> value of 10000, + /// and T is <see cref="System.Byte"/>, this operation would throw an <see cref="System.OverflowException"/> + /// because 10000 is outside the range of the <see cref="System.Byte"/> data type.</exception> + /// <exception cref="System.FormatException">If the conversion from the string representation of this + /// value into another fails because the string is not in the proper format.</exception> + /// <exception cref="System.InvalidCastException">If this instance cannot be read as type T.</exception> + public override object ReadAs(Type type) + { + if (type == null) + { + throw new ArgumentNullException("type"); + } + + object result; + ReadAsFailureKind failure = TryReadAsInternal(type, out result); + if (failure == ReadAsFailureKind.NoFailure) + { + return result; + } + else + { + string valueStr = value.ToString(); + string typeOfTName = type.Name; + switch (failure) + { + case ReadAsFailureKind.InvalidFormat: + throw new FormatException(RS.Format(Properties.Resources.CannotReadPrimitiveAsType, valueStr, typeOfTName)); + case ReadAsFailureKind.InvalidDateFormat: + throw new FormatException(RS.Format(Properties.Resources.InvalidDateFormat, valueStr, typeOfTName)); + case ReadAsFailureKind.InvalidUriFormat: + throw new UriFormatException(RS.Format(Properties.Resources.InvalidUriFormat, jsonPrimitiveType.Name, valueStr, typeOfTName, uriType.Name)); + case ReadAsFailureKind.Overflow: + throw new OverflowException(RS.Format(Properties.Resources.OverflowReadAs, valueStr, typeOfTName)); + case ReadAsFailureKind.InvalidCast: + default: + throw new InvalidCastException(RS.Format(Properties.Resources.CannotReadPrimitiveAsType, valueStr, typeOfTName)); + } + } + } + + /// <summary> + /// Attempts to convert this <see cref="System.Json.JsonPrimitive"/> instance into an instance of the specified type. + /// </summary> + /// <param name="type">The type to which the conversion is being performed.</param> + /// <param name="value">An object instance to be initialized with this instance or null if the conversion cannot be performed.</param> + /// <returns>true if this <see cref="System.Json.JsonPrimitive"/> instance can be read as the specified type; otherwise, false.</returns> + [SuppressMessage("Microsoft.Maintainability", "CA1500:VariableNamesShouldNotMatchFieldNames", MessageId = "value", + Justification = "field is used with 'this' and arg is out param which makes it harder to be misused.")] + public override bool TryReadAs(Type type, out object value) + { + return TryReadAsInternal(type, out value) == ReadAsFailureKind.NoFailure; + } + + /// <summary> + /// Returns the value this object wraps (if any). + /// </summary> + /// <returns>The value wrapped by this instance or null if none.</returns> + internal override object Read() + { + return value; + } + + internal override void Save(XmlDictionaryWriter jsonWriter) + { + if (value == null) + { + throw new ArgumentNullException("jsonWriter"); + } + + switch (jsonType) + { + case JsonType.Boolean: + jsonWriter.WriteAttributeString(JXmlToJsonValueConverter.TypeAttributeName, JXmlToJsonValueConverter.BooleanAttributeValue); + break; + case JsonType.Number: + jsonWriter.WriteAttributeString(JXmlToJsonValueConverter.TypeAttributeName, JXmlToJsonValueConverter.NumberAttributeValue); + break; + default: + jsonWriter.WriteAttributeString(JXmlToJsonValueConverter.TypeAttributeName, JXmlToJsonValueConverter.StringAttributeValue); + break; + } + + WriteValue(jsonWriter); + } + + private static ConvertResult StringToBool(string valueString) + { + ConvertResult result = new ConvertResult(); + bool tempBool; + result.ReadAsFailureKind = Boolean.TryParse(valueString, out tempBool) ? ReadAsFailureKind.NoFailure : ReadAsFailureKind.InvalidFormat; + result.Value = tempBool; + return result; + } + + private static ConvertResult StringToByte(string valueString) + { + ConvertResult result = new ConvertResult(); + byte tempByte; + result.ReadAsFailureKind = Byte.TryParse(valueString, out tempByte) ? ReadAsFailureKind.NoFailure : ReadAsFailureKind.InvalidCast; + if (result.ReadAsFailureKind != ReadAsFailureKind.NoFailure) + { + result.ReadAsFailureKind = StringToNumberConverter<byte>(valueString, out tempByte); + } + + result.Value = tempByte; + return result; + } + + private static ConvertResult StringToChar(string valueString) + { + ConvertResult result = new ConvertResult(); + char tempChar; + result.ReadAsFailureKind = Char.TryParse(valueString, out tempChar) ? ReadAsFailureKind.NoFailure : ReadAsFailureKind.InvalidFormat; + result.Value = tempChar; + return result; + } + + private static ConvertResult StringToDecimal(string valueString) + { + ConvertResult result = new ConvertResult(); + decimal tempDecimal; + result.ReadAsFailureKind = Decimal.TryParse(valueString, NumberStyles.Float, NumberFormatInfo.InvariantInfo, out tempDecimal) ? ReadAsFailureKind.NoFailure : ReadAsFailureKind.InvalidCast; + if (result.ReadAsFailureKind != ReadAsFailureKind.NoFailure) + { + result.ReadAsFailureKind = StringToNumberConverter<decimal>(valueString, out tempDecimal); + } + + result.Value = tempDecimal; + return result; + } + + private static ConvertResult StringToDateTime(string valueString) + { + ConvertResult result = new ConvertResult(); + DateTime tempDateTime; + result.ReadAsFailureKind = TryParseDateTime(valueString, out tempDateTime) ? ReadAsFailureKind.NoFailure : ReadAsFailureKind.InvalidDateFormat; + result.Value = tempDateTime; + return result; + } + + private static ConvertResult StringToDateTimeOffset(string valueString) + { + ConvertResult result = new ConvertResult(); + DateTimeOffset tempDateTimeOffset; + result.ReadAsFailureKind = TryParseDateTimeOffset(valueString, out tempDateTimeOffset) ? ReadAsFailureKind.NoFailure : ReadAsFailureKind.InvalidDateFormat; + result.Value = tempDateTimeOffset; + return result; + } + + private static ConvertResult StringToDouble(string valueString) + { + ConvertResult result = new ConvertResult(); + double tempDouble; + result.ReadAsFailureKind = Double.TryParse(valueString, NumberStyles.Float, NumberFormatInfo.InvariantInfo, out tempDouble) ? ReadAsFailureKind.NoFailure : ReadAsFailureKind.InvalidCast; + if (result.ReadAsFailureKind != ReadAsFailureKind.NoFailure) + { + result.ReadAsFailureKind = StringToNumberConverter<double>(valueString, out tempDouble); + } + + result.Value = tempDouble; + return result; + } + + private static bool TryGuidParse (string value, out Guid guid) + { +#if NET_4_0 + return Guid.TryParse (value, out guid); +#else + try { + guid = new Guid (value); + return true; + } catch (Exception) { + guid = Guid.Empty; + return false; + } +#endif + } + + private static ConvertResult StringToGuid(string valueString) + { + ConvertResult result = new ConvertResult(); + Guid tempGuid; + result.ReadAsFailureKind = TryGuidParse(valueString, out tempGuid) ? ReadAsFailureKind.NoFailure : ReadAsFailureKind.InvalidFormat; + result.Value = tempGuid; + return result; + } + + private static ConvertResult StringToShort(string valueString) + { + ConvertResult result = new ConvertResult(); + short tempShort; + result.ReadAsFailureKind = Int16.TryParse(valueString, NumberStyles.Integer, NumberFormatInfo.InvariantInfo, out tempShort) ? ReadAsFailureKind.NoFailure : ReadAsFailureKind.InvalidCast; + if (result.ReadAsFailureKind != ReadAsFailureKind.NoFailure) + { + result.ReadAsFailureKind = StringToNumberConverter<short>(valueString, out tempShort); + } + + result.Value = tempShort; + return result; + } + + private static ConvertResult StringToInt(string valueString) + { + ConvertResult result = new ConvertResult(); + int tempInt; + result.ReadAsFailureKind = Int32.TryParse(valueString, NumberStyles.Integer, NumberFormatInfo.InvariantInfo, out tempInt) ? ReadAsFailureKind.NoFailure : ReadAsFailureKind.InvalidCast; + if (result.ReadAsFailureKind != ReadAsFailureKind.NoFailure) + { + result.ReadAsFailureKind = StringToNumberConverter<int>(valueString, out tempInt); + } + + result.Value = tempInt; + return result; + } + + private static ConvertResult StringToLong(string valueString) + { + ConvertResult result = new ConvertResult(); + long tempLong; + result.ReadAsFailureKind = Int64.TryParse(valueString, NumberStyles.Integer, NumberFormatInfo.InvariantInfo, out tempLong) ? ReadAsFailureKind.NoFailure : ReadAsFailureKind.InvalidCast; + if (result.ReadAsFailureKind != ReadAsFailureKind.NoFailure) + { + result.ReadAsFailureKind = StringToNumberConverter<long>(valueString, out tempLong); + } + + result.Value = tempLong; + return result; + } + + private static ConvertResult StringToSByte(string valueString) + { + ConvertResult result = new ConvertResult(); + sbyte tempSByte; + result.ReadAsFailureKind = SByte.TryParse(valueString, NumberStyles.Integer, NumberFormatInfo.InvariantInfo, out tempSByte) ? ReadAsFailureKind.NoFailure : ReadAsFailureKind.InvalidCast; + if (result.ReadAsFailureKind != ReadAsFailureKind.NoFailure) + { + result.ReadAsFailureKind = StringToNumberConverter<sbyte>(valueString, out tempSByte); + } + + result.Value = tempSByte; + return result; + } + + private static ConvertResult StringToFloat(string valueString) + { + ConvertResult result = new ConvertResult(); + float tempFloat; + result.ReadAsFailureKind = Single.TryParse(valueString, NumberStyles.Float, NumberFormatInfo.InvariantInfo, out tempFloat) ? ReadAsFailureKind.NoFailure : ReadAsFailureKind.InvalidCast; + if (result.ReadAsFailureKind != ReadAsFailureKind.NoFailure) + { + result.ReadAsFailureKind = StringToNumberConverter<float>(valueString, out tempFloat); + } + + result.Value = tempFloat; + return result; + } + + private static ConvertResult StringToUShort(string valueString) + { + ConvertResult result = new ConvertResult(); + ushort tempUShort; + result.ReadAsFailureKind = UInt16.TryParse(valueString, NumberStyles.Integer, NumberFormatInfo.InvariantInfo, out tempUShort) ? ReadAsFailureKind.NoFailure : ReadAsFailureKind.InvalidCast; + if (result.ReadAsFailureKind != ReadAsFailureKind.NoFailure) + { + result.ReadAsFailureKind = StringToNumberConverter<ushort>(valueString, out tempUShort); + } + + result.Value = tempUShort; + return result; + } + + private static ConvertResult StringToUInt(string valueString) + { + ConvertResult result = new ConvertResult(); + uint tempUInt; + result.ReadAsFailureKind = UInt32.TryParse(valueString, NumberStyles.Integer, NumberFormatInfo.InvariantInfo, out tempUInt) ? ReadAsFailureKind.NoFailure : ReadAsFailureKind.InvalidCast; + if (result.ReadAsFailureKind != ReadAsFailureKind.NoFailure) + { + result.ReadAsFailureKind = StringToNumberConverter<uint>(valueString, out tempUInt); + } + + result.Value = tempUInt; + return result; + } + + private static ConvertResult StringToULong(string valueString) + { + ConvertResult result = new ConvertResult(); + ulong tempULong; + result.ReadAsFailureKind = UInt64.TryParse(valueString, NumberStyles.Integer, NumberFormatInfo.InvariantInfo, out tempULong) ? ReadAsFailureKind.NoFailure : ReadAsFailureKind.InvalidCast; + if (result.ReadAsFailureKind != ReadAsFailureKind.NoFailure) + { + result.ReadAsFailureKind = StringToNumberConverter<ulong>(valueString, out tempULong); + } + + result.Value = tempULong; + return result; + } + + private static ConvertResult StringToUri(string valueString) + { + ConvertResult result = new ConvertResult(); + Uri tempUri; + result.ReadAsFailureKind = Uri.TryCreate(valueString, UriKind.RelativeOrAbsolute, out tempUri) ? ReadAsFailureKind.NoFailure : ReadAsFailureKind.InvalidUriFormat; + result.Value = tempUri; + return result; + } + + private static ReadAsFailureKind StringToNumberConverter<T>(string valueString, out T valueNumber) + { + string str = valueString.Trim(); + + if (str.IndexOfAny(FloatingPointChars) < 0) + { + long longVal; + if (Int64.TryParse(str, NumberStyles.Float, CultureInfo.InvariantCulture, out longVal)) + { + return NumberToNumberConverter<T>(longVal, out valueNumber); + } + } + + decimal decValue; + if (Decimal.TryParse(str, NumberStyles.Float, CultureInfo.InvariantCulture, out decValue) && decValue != 0) + { + return NumberToNumberConverter<T>(decValue, out valueNumber); + } + + double dblValue; + if (Double.TryParse(str, NumberStyles.Float, CultureInfo.InvariantCulture, out dblValue)) + { + return NumberToNumberConverter<T>(dblValue, out valueNumber); + } + + valueNumber = default(T); + return ReadAsFailureKind.InvalidFormat; + } + + private static ReadAsFailureKind NumberToNumberConverter<T>(object valueObject, out T valueNumber) + { + object value; + ReadAsFailureKind failureKind = NumberToNumberConverter(typeof(T), valueObject, out value); + if (failureKind == ReadAsFailureKind.NoFailure) + { + valueNumber = (T)value; + } + else + { + valueNumber = default(T); + } + + return failureKind; + } + + private static ReadAsFailureKind NumberToNumberConverter(Type type, object valueObject, out object valueNumber) + { + try + { + valueNumber = System.Convert.ChangeType(valueObject, type, CultureInfo.InvariantCulture); + return ReadAsFailureKind.NoFailure; + } + catch (OverflowException) + { + valueNumber = null; + return ReadAsFailureKind.Overflow; + } + } + + private static bool TryParseDateTime(string valueString, out DateTime dateTime) + { + string filteredValue = valueString.EndsWith(UtcString, StringComparison.Ordinal) ? valueString.Replace(UtcString, GmtString) : valueString; + + if (DateTime.TryParse(filteredValue, CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind, out dateTime)) + { + return true; + } + + if (TryParseAspNetDateTimeFormat(valueString, out dateTime)) + { + return true; + } + + return false; + } + + private static bool TryParseDateTimeOffset(string valueString, out DateTimeOffset dateTimeOffset) + { + string filteredValue = valueString.EndsWith(UtcString, StringComparison.Ordinal) ? valueString.Replace(UtcString, GmtString) : valueString; + + if (DateTimeOffset.TryParse(filteredValue, CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind, out dateTimeOffset)) + { + return true; + } + + if (TryParseAspNetDateTimeFormat(valueString, out dateTimeOffset)) + { + return true; + } + + return false; + } + + private static bool TryParseAspNetDateTimeFormat(string valueString, out DateTime dateTime) + { + // Reference to the format is available at these sources: + // http://msdn.microsoft.com/en-us/library/bb299886.aspx#intro_to_json_sidebarb + // http://msdn.microsoft.com/en-us/library/bb412170.aspx + + // The format for the value is given by the following regex: + // \/Date\((?<milliseconds>\-?\d+)(?<offset>[\+\-]?\d{4})\)\/ + // where milliseconds is the number of milliseconds since 1970/01/01:00:00:00.000 UTC (the "unix baseline") + // and offset is an optional which indicates whether the value is local or UTC. + // The actual value of the offset is ignored, since the ticks represent the UTC offset. The value is converted to local time based on that info. + const string DateTimePrefix = "/Date("; + const int DateTimePrefixLength = 6; + const string DateTimeSuffix = ")/"; + const int DateTimeSuffixLength = 2; + + if (valueString.StartsWith(DateTimePrefix, StringComparison.Ordinal) && valueString.EndsWith(DateTimeSuffix, StringComparison.Ordinal)) + { + string ticksValue = valueString.Substring(DateTimePrefixLength, valueString.Length - DateTimePrefixLength - DateTimeSuffixLength); + DateTimeKind dateTimeKind = DateTimeKind.Utc; + + int indexOfTimeZoneOffset = ticksValue.IndexOf('+', 1); + + if (indexOfTimeZoneOffset < 0) + { + indexOfTimeZoneOffset = ticksValue.IndexOf('-', 1); + } + + // If an offset is present, verify it is properly formatted. Actual value is ignored (see spec). + if (indexOfTimeZoneOffset != -1) + { + if (indexOfTimeZoneOffset + 5 == ticksValue.Length + && IsLatinDigit(ticksValue[indexOfTimeZoneOffset + 1]) + && IsLatinDigit(ticksValue[indexOfTimeZoneOffset + 2]) + && IsLatinDigit(ticksValue[indexOfTimeZoneOffset + 3]) + && IsLatinDigit(ticksValue[indexOfTimeZoneOffset + 4])) + { + ticksValue = ticksValue.Substring(0, indexOfTimeZoneOffset); + dateTimeKind = DateTimeKind.Local; + } + else + { + dateTime = new DateTime(); + return false; + } + } + + long millisecondsSinceUnixEpoch; + if (Int64.TryParse(ticksValue, NumberStyles.Integer, CultureInfo.InvariantCulture, out millisecondsSinceUnixEpoch)) + { + long ticks = (millisecondsSinceUnixEpoch * 10000) + UnixEpochTicks; + if (ticks < DateTime.MaxValue.Ticks) + { + dateTime = new DateTime(ticks, DateTimeKind.Utc); + if (dateTimeKind == DateTimeKind.Local) + { + dateTime = dateTime.ToLocalTime(); + } + + return true; + } + } + } + + dateTime = new DateTime(); + return false; + } + + private static bool TryParseAspNetDateTimeFormat(string valueString, out DateTimeOffset dateTimeOffset) + { + DateTime dateTime; + if (TryParseAspNetDateTimeFormat(valueString, out dateTime)) + { + dateTimeOffset = new DateTimeOffset(dateTime); + return true; + } + + dateTimeOffset = new DateTimeOffset(); + return false; + } + + private static bool IsLatinDigit(char c) + { + return (c >= '0') && (c <= '9'); + } + + private static string UnescapeJsonString(string val) + { + if (val == null) + { + return null; + } + + StringBuilder sb = null; + int startIndex = 0, count = 0; + for (int i = 0; i < val.Length; i++) + { + if (val[i] == '\\') + { + i++; + if (sb == null) + { + sb = new StringBuilder(); + } + + sb.Append(val, startIndex, count); +#if NET_4_0 + Contract.Assert(i < val.Length, "Found that a '\' was the last character in a string, which is invalid JSON. Verify the calling method uses a valid JSON string as the input parameter of this method."); +#endif + switch (val[i]) + { + case '"': + case '\'': + case '/': + case '\\': + sb.Append(val[i]); + 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; + case 'u': +#if NET_4_0 + Contract.Assert((i + 3) < val.Length, String.Format(CultureInfo.CurrentCulture, "Unexpected char {0} at position {1}. The unicode escape sequence should be followed by 4 digits.", val[i], i)); +#endif + sb.Append(ParseChar(val.Substring(i + 1, 4), NumberStyles.HexNumber)); + i += 4; + break; + } + + startIndex = i + 1; + count = 0; + } + else + { + count++; + } + } + + if (sb == null) + { + return val; + } + + if (count > 0) + { + sb.Append(val, startIndex, count); + } + + return sb.ToString(); + } + + private static char ParseChar(string value, NumberStyles style) + { + try + { + int intValue = Int32.Parse(value, style, NumberFormatInfo.InvariantInfo); + return System.Convert.ToChar(intValue); + } + catch (ArgumentException exception) + { + throw new InvalidCastException(exception.Message, exception); + } + catch (FormatException exception) + { + throw new InvalidCastException(exception.Message, exception); + } + catch (OverflowException exception) + { + throw new InvalidCastException(exception.Message, exception); + } + } + + [SuppressMessage("Microsoft.Maintainability", "CA1500:VariableNamesShouldNotMatchFieldNames", MessageId = "value", + Justification = "field is used with 'this' and arg is out param which makes it harder to be misused.")] + private ReadAsFailureKind TryReadAsInternal(Type type, out object value) + { + if (base.TryReadAs(type, out value)) + { + return ReadAsFailureKind.NoFailure; + } + + if (type == this.value.GetType()) + { + value = this.value; + return ReadAsFailureKind.NoFailure; + } + + if (jsonType == JsonType.Number) + { + switch (Type.GetTypeCode(type)) + { + case TypeCode.Byte: + case TypeCode.SByte: + case TypeCode.Int16: + case TypeCode.Int32: + case TypeCode.Int64: + case TypeCode.UInt16: + case TypeCode.UInt32: + case TypeCode.UInt64: + case TypeCode.Single: + case TypeCode.Double: + case TypeCode.Decimal: + return NumberToNumberConverter(type, this.value, out value); + case TypeCode.String: + value = ToString(); + return ReadAsFailureKind.NoFailure; + } + } + + if (jsonType == JsonType.Boolean) + { + if (type == typeof(string)) + { + value = ToString(); + return ReadAsFailureKind.NoFailure; + } + } + + if (jsonType == JsonType.String) + { + string str = UnescapeJsonString(ToString()); +#if NET_4_0 + Contract.Assert(str.Length >= 2 && str.StartsWith("\"", StringComparison.Ordinal) && str.EndsWith("\"", StringComparison.Ordinal), "The unescaped string must begin and end with quotes."); +#endif + str = str.Substring(1, str.Length - 2); + + if (stringConverters.ContainsKey(type)) + { + ConvertResult result = stringConverters[type].Invoke(str); + value = result.Value; + return result.ReadAsFailureKind; + } + + if (type == typeof(string)) + { + value = str; + return ReadAsFailureKind.NoFailure; + } + } + + value = null; + return ReadAsFailureKind.InvalidCast; + } + + private void WriteValue(XmlDictionaryWriter jsonWriter) + { + Type valueType = value.GetType(); + switch (Type.GetTypeCode(valueType)) + { + case TypeCode.Boolean: + jsonWriter.WriteValue((bool)value); + break; + case TypeCode.Byte: + case TypeCode.Int16: + case TypeCode.Int32: + case TypeCode.Int64: + case TypeCode.SByte: + case TypeCode.UInt16: + case TypeCode.UInt32: + case TypeCode.UInt64: + case TypeCode.Decimal: + jsonWriter.WriteValue(String.Format(CultureInfo.InvariantCulture, "{0}", value)); + break; + case TypeCode.Single: + case TypeCode.Double: + jsonWriter.WriteValue(String.Format(CultureInfo.InvariantCulture, "{0:R}", value)); + break; + case TypeCode.Char: + jsonWriter.WriteValue(new string((char)value, 1)); + break; + case TypeCode.String: + jsonWriter.WriteValue((string)value); + break; + case TypeCode.DateTime: + jsonWriter.WriteValue(((DateTime)value).ToString(DateTimeIsoFormat, CultureInfo.InvariantCulture)); + break; + default: + if (valueType == typeof(Uri)) + { + Uri uri = (Uri)value; + jsonWriter.WriteValue(uri.GetComponents(UriComponents.SerializationInfoString, UriFormat.UriEscaped)); + } + else if (valueType == typeof(DateTimeOffset)) + { + jsonWriter.WriteValue(((DateTimeOffset)value).ToString(DateTimeIsoFormat, CultureInfo.InvariantCulture)); + } + else + { + jsonWriter.WriteValue(value); + } + + break; + } + } + + private class ConvertResult + { + public ReadAsFailureKind ReadAsFailureKind { get; set; } + + public object Value { get; set; } + } + } +} diff --git a/mcs/class/System.Json/System.Json/JsonType.cs b/mcs/class/System.Json/System.Json/JsonType.cs new file mode 100644 index 00000000000..785026c32d7 --- /dev/null +++ b/mcs/class/System.Json/System.Json/JsonType.cs @@ -0,0 +1,50 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. + +namespace System.Json +{ + /// <summary> + /// An enumeration that specifies primitive and structured JavaScript Object + /// Notation (JSON) common language runtime (CLR) types. + /// </summary> + public enum JsonType + { + /// <summary> + /// Specifies the JSON string CLR type. + /// </summary> + String, + + /// <summary> + /// Specifies the JSON number CLR type. + /// </summary> + Number, + + /// <summary> + /// Specifies the JSON object CLR type that consists of an unordered collection + /// of key/value pairs, where the key is of type String and the value is of + /// type <see cref="System.Json.JsonValue"/>, which can, in turn, be either a + /// primitive or a structured JSON type. + /// </summary> + Object, + + /// <summary> + /// Specifies the JSON array CLR type that consists of an ordered collection of + /// <see cref="System.Json.JsonValue"/>types, which can, in turn, be either + /// primitive or structured JSON types. + /// </summary> + Array, + + /// <summary> + /// Specifies the JSON Boolean CLR type. + /// </summary> + Boolean, + + /// <summary> + /// Specifies the type returned by calls to <see cref="System.Json.JsonValue.ValueOrDefault(string)"/> + /// or <see cref="System.Json.JsonValue.ValueOrDefault(int)"/> + /// when the element searches doesn't exist in the JSON collection. This is a special + /// value which does not represent any JSON element, and cannot be added to any + /// JSON collections. + /// </summary> + Default + } +} diff --git a/mcs/class/System.Json/System.Json/JsonValue.cs b/mcs/class/System.Json/System.Json/JsonValue.cs new file mode 100644 index 00000000000..97dbae5517e --- /dev/null +++ b/mcs/class/System.Json/System.Json/JsonValue.cs @@ -0,0 +1,1249 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. + +using System.Collections; +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; +#if FEATURE_DYNAMIC +using System.Dynamic; +#endif +using System.Globalization; +using System.IO; +using System.Linq.Expressions; +using System.Runtime.Serialization; +using System.Runtime.Serialization.Json; +using System.Text; +using System.Xml; + +namespace System.Json +{ + /// <summary> + /// This is the base class for JavaScript Object Notation (JSON) common language runtime (CLR) types. + /// </summary> + [SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix", + Justification = "JsonValue is by definition either a collection or a single object.")] + [DataContract] +#if FEATURE_DYNAMIC + public class JsonValue : IEnumerable<KeyValuePair<string, JsonValue>>, IDynamicMetaObjectProvider +#else + public class JsonValue : IEnumerable<KeyValuePair<string, JsonValue>> +#endif + { + private static JsonValue defaultInstance = new JsonValue(); + private int changingListenersCount; + private int changedListenersCount; + + internal JsonValue() + { + } + + /// <summary> + /// Raised when this <see cref="System.Json.JsonValue"/> or any of its members have changed. + /// </summary> + /// <remarks><p>Events are raised when elements are added or removed to <see cref="System.Json.JsonValue"/> + /// instances. It applies to both complex descendants of <see cref="System.Json.JsonValue"/>: <see cref="System.Json.JsonArray"/> + /// and <see cref="System.Json.JsonObject"/>.</p> + /// <p>You should be careful when modifying a <see cref="System.Json.JsonValue"/> tree within one of these events, + /// because doing this might lead to unexpected results. For example, if you receive a Changing event, and while + /// the event is being processed you remove the node from the tree, you might not receive the Changed event. When + /// an event is being processed, it is valid to modify a tree other than the one that contains the node that is + /// receiving the event; it is even valid to modify the same tree provided the modifications do not affect the + /// specific nodes on which the event was raised. However, if you modify the area of the tree that contains the + /// node receiving the event, the events that you receive and the impact to the tree are undefined.</p></remarks> + public event EventHandler<JsonValueChangeEventArgs> Changed + { + add + { + changedListenersCount++; + OnChanged += value; + } + + remove + { + changedListenersCount--; + OnChanged -= value; + } + } + + /// <summary> + /// Raised when this <see cref="System.Json.JsonValue"/> or any of its members are about to be changed. + /// </summary> + /// <remarks><p>Events are raised when elements are added or removed to <see cref="System.Json.JsonValue"/> + /// instances. It applies to both complex descendants of <see cref="System.Json.JsonValue"/>: <see cref="System.Json.JsonArray"/> + /// and <see cref="System.Json.JsonObject"/>.</p> + /// <p>You should be careful when modifying a <see cref="System.Json.JsonValue"/> tree within one of these events, + /// because doing this might lead to unexpected results. For example, if you receive a Changing event, and while + /// the event is being processed you remove the node from the tree, you might not receive the Changed event. When + /// an event is being processed, it is valid to modify a tree other than the one that contains the node that is + /// receiving the event; it is even valid to modify the same tree provided the modifications do not affect the + /// specific nodes on which the event was raised. However, if you modify the area of the tree that contains the + /// node receiving the event, the events that you receive and the impact to the tree are undefined.</p></remarks> + public event EventHandler<JsonValueChangeEventArgs> Changing + { + add + { + changingListenersCount++; + OnChanging += value; + } + + remove + { + changingListenersCount--; + OnChanging -= value; + } + } + + private event EventHandler<JsonValueChangeEventArgs> OnChanged; + private event EventHandler<JsonValueChangeEventArgs> OnChanging; + + /// <summary> + /// Gets the JSON CLR type represented by this instance. + /// </summary> + public virtual JsonType JsonType + { + get { return JsonType.Default; } + } + + /// <summary> + /// Gets the number of items in this object. + /// </summary> + public virtual int Count + { + get { return 0; } + } + + /// <summary> + /// Gets the number of listeners to the <see cref="Changing"/> event for this instance. + /// </summary> + protected int ChangingListenersCount + { + get { return changingListenersCount; } + } + + /// <summary> + /// Gets the number of listeners to the <see cref="Changed"/> event for this instance. + /// </summary> + protected int ChangedListenersCount + { + get { return changedListenersCount; } + } + + /// <summary> + /// Gets the default JsonValue instance. + /// This instance enables safe-chaining of JsonValue operations and resolves to 'null' + /// when this instance is used as dynamic, mapping to the JavaScript 'null' value. + /// </summary> + private static JsonValue DefaultInstance + { + get { return defaultInstance; } + } + + /// <summary> + /// This indexer is not supported for this base class and throws an exception. + /// </summary> + /// <param name="key">The key of the element to get or set.</param> + /// <returns><see cref="System.Json.JsonValue"/>.</returns> + /// <remarks>The exception thrown is the <see cref="System.InvalidOperationException"/>. + /// This method is overloaded in the implementation of the <see cref="System.Json.JsonObject"/> + /// class, which inherits from this class.</remarks> + public virtual JsonValue this[string key] + { + get { throw new InvalidOperationException(RS.Format(Properties.Resources.IndexerNotSupportedOnJsonType, typeof(string), JsonType)); } + + set { throw new InvalidOperationException(RS.Format(Properties.Resources.IndexerNotSupportedOnJsonType, typeof(string), JsonType)); } + } + + /// <summary> + /// This indexer is not supported for this base class and throws an exception. + /// </summary> + /// <param name="index">The zero-based index of the element to get or set.</param> + /// <returns><see cref="System.Json.JsonValue"/>.</returns> + /// <remarks>The exception thrown is the <see cref="System.InvalidOperationException"/>. + /// This method is overloaded in the implementation of the <see cref="System.Json.JsonArray"/> + /// class, which inherits from this class.</remarks> + public virtual JsonValue this[int index] + { + get { throw new InvalidOperationException(RS.Format(Properties.Resources.IndexerNotSupportedOnJsonType, typeof(int), JsonType)); } + + set { throw new InvalidOperationException(RS.Format(Properties.Resources.IndexerNotSupportedOnJsonType, typeof(int), JsonType)); } + } + + /// <summary> + /// Deserializes text-based JSON into a JSON CLR type. + /// </summary> + /// <param name="json">The text-based JSON to be parsed into a JSON CLR type.</param> + /// <returns>The <see cref="System.Json.JsonValue"/> object that represents the parsed + /// text-based JSON as a CLR type.</returns> + /// <exception cref="System.ArgumentException">The length of jsonString is zero.</exception> + /// <exception cref="System.ArgumentNullException">jsonString is null.</exception> + /// <remarks>The result will be an instance of either <see cref="System.Json.JsonArray"/>, + /// <see cref="System.Json.JsonObject"/> or <see cref="System.Json.JsonPrimitive"/>, + /// depending on the text-based JSON supplied to the method.</remarks> + public static JsonValue Parse(string json) + { + return JXmlToJsonValueConverter.JXMLToJsonValue(json); + } + + /// <summary> + /// Deserializes text-based JSON from a text reader into a JSON CLR type. + /// </summary> + /// <param name="textReader">A <see cref="System.IO.TextReader"/> over text-based JSON content.</param> + /// <returns>The <see cref="System.Json.JsonValue"/> object that represents the parsed + /// text-based JSON as a CLR type.</returns> + /// <exception cref="System.ArgumentNullException">textReader is null.</exception> + /// <remarks>The result will be an instance of either <see cref="System.Json.JsonArray"/>, + /// <see cref="System.Json.JsonObject"/> or <see cref="System.Json.JsonPrimitive"/>, + /// depending on the text-based JSON supplied to the method.</remarks> + public static JsonValue Load(TextReader textReader) + { + if (textReader == null) + { + throw new ArgumentNullException("textReader"); + } + + return JsonValue.Parse(textReader.ReadToEnd()); + } + + /// <summary> + /// Deserializes text-based JSON from a stream into a JSON CLR type. + /// </summary> + /// <param name="stream">A <see cref="System.IO.Stream"/> that contains text-based JSON content.</param> + /// <returns>The <see cref="System.Json.JsonValue"/> object that represents the parsed + /// text-based JSON as a CLR type.</returns> + /// <exception cref="System.ArgumentNullException">stream is null.</exception> + /// <remarks>The result will be an instance of either <see cref="System.Json.JsonArray"/>, + /// <see cref="System.Json.JsonObject"/> or <see cref="System.Json.JsonPrimitive"/>, + /// depending on the text-based JSON supplied to the method.</remarks> + public static JsonValue Load(Stream stream) + { + return JXmlToJsonValueConverter.JXMLToJsonValue(stream); + } + + /// <summary> + /// Performs a cast operation from a <see cref="JsonValue"/> instance into the specified type parameter./> + /// </summary> + /// <typeparam name="T">The type to cast the instance to.</typeparam> + /// <param name="value">The <see cref="System.Json.JsonValue"/> instance.</param> + /// <returns>An object of type T initialized with the <see cref="System.Json.JsonValue"/> value specified.</returns> + /// <remarks>This method is to support the framework and is not intended to be used externally, use explicit type cast instead.</remarks> + [EditorBrowsable(EditorBrowsableState.Never)] + [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", + Justification = "The generic parameter is used to specify the output type")] + public static T CastValue<T>(JsonValue value) + { + Type typeofT = typeof(T); + + if ((value != null && typeofT.IsAssignableFrom(value.GetType())) || typeofT == typeof(object)) + { + return (T)(object)value; + } + + if (value == null || value.JsonType == JsonType.Default) + { + if (typeofT.IsValueType) + { + throw new InvalidCastException(RS.Format(Properties.Resources.InvalidCastNonNullable, typeofT.FullName)); + } + else + { + return default(T); + } + } + + try + { + return value.ReadAs<T>(); + } + catch (Exception ex) + { + if (ex is FormatException || ex is NotSupportedException || ex is InvalidCastException) + { + throw new InvalidCastException(RS.Format(Properties.Resources.CannotCastJsonValue, value.GetType().FullName, typeofT.FullName), ex); + } + + throw; + } + } + + /// <summary> + /// Returns an enumerator which iterates through the values in this object. + /// </summary> + /// <returns>An enumerator which which iterates through the values in this object.</returns> + /// <remarks>The enumerator returned by this class is empty; subclasses will override this method to return appropriate enumerators for themselves.</remarks> + public IEnumerator<KeyValuePair<string, JsonValue>> GetEnumerator() + { + return GetKeyValuePairEnumerator(); + } + + /// <summary> + /// Returns an enumerator which iterates through the values in this object. + /// </summary> + /// <returns>An <see cref="System.Collections.IEnumerator"/> which iterates through the values in this object.</returns> + /// <remarks>The enumerator returned by this class is empty; subclasses will override this method to return appropriate enumerators for themselves.</remarks> + IEnumerator IEnumerable.GetEnumerator() + { + return GetKeyValuePairEnumerator(); + } + +#if FEATURE_DYNAMIC + /// <summary> + /// Gets this instance as a <code>dynamic</code> object. + /// </summary> + /// <returns>This instance as <code>dynamic</code>.</returns> + public dynamic AsDynamic() + { + return this; + } +#endif + + /// <summary> + /// Attempts to convert this <see cref="System.Json.JsonValue"/> instance into the type T. + /// </summary> + /// <typeparam name="T">The type to which the conversion is being performed.</typeparam> + /// <param name="valueOfT">An instance of T initialized with this instance, or the default value of T if the conversion cannot be performed.</param> + /// <returns>true if this <see cref="System.Json.JsonValue"/> instance can be read as type T; otherwise, false.</returns> + public bool TryReadAs<T>(out T valueOfT) + { + object value; + if (TryReadAs(typeof(T), out value)) + { + valueOfT = (T)value; + return true; + } + + valueOfT = default(T); + return false; + } + + /// <summary> + /// Attempts to convert this <see cref="System.Json.JsonValue"/> instance into the type T. + /// </summary> + /// <typeparam name="T">The type to which the conversion is being performed.</typeparam> + /// <returns>An instance of T initialized with the value from the conversion of this instance.</returns> + /// <exception cref="System.NotSupportedException">If this <see cref="System.Json.JsonValue"/> value cannot be converted into the type T.</exception> + [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", + Justification = "The generic parameter is used to specify the output type")] + public T ReadAs<T>() + { + return (T)ReadAs(typeof(T)); + } + + /// <summary> + /// Attempts to convert this <see cref="System.Json.JsonValue"/> instance into the type T. + /// </summary> + /// <typeparam name="T">The type to which the conversion is being performed.</typeparam> + /// <param name="fallback">The fallback value to be returned if the conversion cannot be made.</param> + /// <returns>An instance of T initialized with the value from the conversion of this instance, or the specified fallback value if the conversion cannot be made.</returns> + public T ReadAs<T>(T fallback) + { + return (T)ReadAs(typeof(T), fallback); + } + + /// <summary> + /// Attempts to convert this <see cref="System.Json.JsonValue"/> instance to an instance of the specified type. + /// </summary> + /// <param name="type">The type to which the conversion is being performed.</param> + /// <param name="fallback">The fallback value to be returned if the conversion cannot be made.</param> + /// <returns>An instance of the specified type initialized with the value from the conversion of this instance, or the specified fallback value if the conversion cannot be made.</returns> + public object ReadAs(Type type, object fallback) + { + if (type == null) + { + throw new ArgumentNullException("type"); + } + + object result; + if (JsonType != JsonType.Default && TryReadAs(type, out result)) + { + return result; + } + else + { + return fallback; + } + } + + /// <summary> + /// Attempts to convert this <see cref="System.Json.JsonValue"/> instance into an instance of the specified type. + /// </summary> + /// <param name="type">The type to which the conversion is being performed.</param> + /// <returns>An instance of the specified type initialized with the value from the conversion of this instance.</returns> + /// <exception cref="System.NotSupportedException">If this <see cref="System.Json.JsonValue"/> value cannot be converted into the type T.</exception> + public virtual object ReadAs(Type type) + { + if (type == null) + { + throw new ArgumentNullException("type"); + } + + object result; + if (TryReadAs(type, out result)) + { + return result; + } + + throw new NotSupportedException(RS.Format(Properties.Resources.CannotReadAsType, GetType().FullName, type.FullName)); + } + + /// <summary> + /// Attempts to convert this <see cref="System.Json.JsonValue"/> instance into an instance of the specified type. + /// </summary> + /// <param name="type">The type to which the conversion is being performed.</param> + /// <param name="value">An object to be initialized with this instance or null if the conversion cannot be performed.</param> + /// <returns>true if this <see cref="System.Json.JsonValue"/> instance can be read as the specified type; otherwise, false.</returns> + [SuppressMessage("Microsoft.Design", "CA1007:UseGenericsWhereAppropriate", + Justification = "This is the non-generic version of the method.")] + public virtual bool TryReadAs(Type type, out object value) + { + if (type == null) + { + throw new ArgumentNullException("type"); + } + + if (type.IsAssignableFrom(GetType()) || type == typeof(object)) + { + value = this; + return true; + } + + value = null; + return false; + } + + /// <summary> + /// Writes this <see cref="System.Json.JsonValue"/> instance to a <see cref="System.IO.Stream"/>. + /// </summary> + /// <param name="stream">Stream to which to write text-based JSON.</param> + public void Save(Stream stream) + { + if (JsonType == JsonType.Default) + { + throw new InvalidOperationException(Properties.Resources.UseOfDefaultNotAllowed); + } + + if (stream == null) + { + throw new ArgumentNullException("stream"); + } + + using (XmlDictionaryWriter jsonWriter = JsonReaderWriterFactory.CreateJsonWriter(stream, Encoding.UTF8, false)) + { + jsonWriter.WriteStartElement(JXmlToJsonValueConverter.RootElementName); + Save(jsonWriter); + jsonWriter.WriteEndElement(); + } + } + + /// <summary> + /// Writes <see cref="System.Json.JsonValue"/> instance to a <see cref="TextWriter"/>. + /// </summary> + /// <param name="textWriter">The <see cref="System.IO.TextWriter"/> used to write text-based JSON.</param> + public void Save(TextWriter textWriter) + { + if (JsonType == JsonType.Default) + { + throw new InvalidOperationException(Properties.Resources.UseOfDefaultNotAllowed); + } + + if (textWriter == null) + { + throw new ArgumentNullException("textWriter"); + } + + using (MemoryStream ms = new MemoryStream()) + { + Save(ms); + ms.Position = 0; + textWriter.Write(new StreamReader(ms).ReadToEnd()); + } + } + + /// <summary> + /// Provides a textual representation of this <see cref="System.Json.JsonValue"/> instance. + /// </summary> + /// <returns>A <see cref="System.String"/> containing text-based JSON.</returns> + public override string ToString() + { + if (JsonType == JsonType.Default) + { + return "Default"; + } + + using (MemoryStream ms = new MemoryStream()) + { + Save(ms); + ms.Position = 0; + return new StreamReader(ms).ReadToEnd(); + } + } + + /// <summary> + /// Checks whether a key/value pair with a specified key exists in the JSON CLR object type. + /// </summary> + /// <param name="key">The key to check for.</param> + /// <returns>false in this class; subclasses may override this method to return other values.</returns> + /// <remarks>This method is overloaded in the implementation of the <see cref="System.Json.JsonObject"/> + /// class, which inherits from this class.</remarks> + public virtual bool ContainsKey(string key) + { + return false; + } + + /// <summary> + /// Returns the value returned by the safe string indexer for this instance. + /// </summary> + /// <param name="key">The key of the element to get.</param> + /// <returns>If this is an instance of <see cref="System.Json.JsonObject"/>, it contains + /// the given key and the value corresponding to the key is not null, then it will return that value. + /// Otherwise it will return a <see cref="System.Json.JsonValue"/> instance with <see cref="System.Json.JsonValue.JsonType"/> + /// equals to <see cref="F:System.Json.JsonType.Default"/>.</returns> + [EditorBrowsable(EditorBrowsableState.Never)] + public virtual JsonValue GetValue(string key) + { + return ValueOrDefault(key); + } + + /// <summary> + /// Returns the value returned by the safe int indexer for this instance. + /// </summary> + /// <param name="index">The zero-based index of the element to get.</param> + /// <returns>If this is an instance of <see cref="System.Json.JsonArray"/>, the index is within the array + /// bounds, and the value corresponding to the index is not null, then it will return that value. + /// Otherwise it will return a <see cref="System.Json.JsonValue"/> instance with <see cref="System.Json.JsonValue.JsonType"/> + /// equals to <see cref="F:System.Json.JsonType.Default"/>.</returns> + [EditorBrowsable(EditorBrowsableState.Never)] + public virtual JsonValue GetValue(int index) + { + return ValueOrDefault(index); + } + + /// <summary> + /// Sets the value and returns it. + /// </summary> + /// <param name="key">The key of the element to set.</param> + /// <param name="value">The value to be set.</param> + /// <returns>The value, converted into a JsonValue, set in this collection.</returns> + /// <exception cref="System.ArgumentException">If the value cannot be converted into a JsonValue.</exception> + [EditorBrowsable(EditorBrowsableState.Never)] + public virtual JsonValue SetValue(string key, object value) + { + this[key] = ResolveObject(value); + return this[key]; + } + + /// <summary> + /// Sets the value and returns it. + /// </summary> + /// <param name="index">The zero-based index of the element to set.</param> + /// <param name="value">The value to be set.</param> + /// <returns>The value, converted into a JsonValue, set in this collection.</returns> + /// <exception cref="System.ArgumentException">If the value cannot be converted into a JsonValue.</exception> + [EditorBrowsable(EditorBrowsableState.Never)] + public virtual JsonValue SetValue(int index, object value) + { + this[index] = ResolveObject(value); + return this[index]; + } + + /// <summary> + /// Safe string indexer for the <see cref="System.Json.JsonValue"/> type. + /// </summary> + /// <param name="key">The key of the element to get.</param> + /// <returns>If this is an instance of <see cref="System.Json.JsonObject"/>, it contains + /// the given key and the value corresponding to the key is not null, then it will return that value. + /// Otherwise it will return a <see cref="System.Json.JsonValue"/> instance with <see cref="System.Json.JsonValue.JsonType"/> + /// equals to <see cref="F:System.Json.JsonType.Default"/>.</returns> + public virtual JsonValue ValueOrDefault(string key) + { + return JsonValue.DefaultInstance; + } + + /// <summary> + /// Safe indexer for the <see cref="System.Json.JsonValue"/> type. + /// </summary> + /// <param name="index">The zero-based index of the element to get.</param> + /// <returns>If this is an instance of <see cref="System.Json.JsonArray"/>, the index is within the array + /// bounds, and the value corresponding to the index is not null, then it will return that value. + /// Otherwise it will return a <see cref="System.Json.JsonValue"/> instance with <see cref="System.Json.JsonValue.JsonType"/> + /// equals to <see cref="F:System.Json.JsonType.Default"/>.</returns> + public virtual JsonValue ValueOrDefault(int index) + { + return JsonValue.DefaultInstance; + } + + /// <summary> + /// Safe deep indexer for the <see cref="JsonValue"/> type. + /// </summary> + /// <param name="indexes">The indices to index this type. The indices can be + /// of type <see cref="System.Int32"/> or <see cref="System.String"/>.</param> + /// <returns>A <see cref="JsonValue"/> which is equivalent to calling<see cref="ValueOrDefault(int)"/> or + /// <see cref="ValueOrDefault(string)"/> on the first index, then calling it again on the result + /// for the second index and so on.</returns> + /// <exception cref="System.ArgumentException">If any of the indices is not of type + /// <see cref="System.Int32"/> or <see cref="System.String"/>.</exception> + public JsonValue ValueOrDefault(params object[] indexes) + { + if (indexes == null) + { + return JsonValue.DefaultInstance; + } + + if (indexes.Length == 0) + { + return this; + } + + JsonValue result = this; + for (int i = 0; i < indexes.Length; i++) + { + object index = indexes[i]; + if (index == null) + { + result = JsonValue.DefaultInstance; + continue; + } + + Type indexType = index.GetType(); + + switch (Type.GetTypeCode(indexType)) + { + case TypeCode.Char: + case TypeCode.Int16: + case TypeCode.UInt16: + case TypeCode.Byte: + case TypeCode.SByte: + index = System.Convert.ChangeType(index, typeof(int), CultureInfo.InvariantCulture); + goto case TypeCode.Int32; + + case TypeCode.Int32: + result = result.ValueOrDefault((int)index); + break; + + case TypeCode.String: + result = result.ValueOrDefault((string)index); + break; + + default: + throw new ArgumentException(RS.Format(Properties.Resources.InvalidIndexType, index.GetType()), "indexes"); + } + } + + return result; + } + +#if FEATURE_DYNAMIC + [SuppressMessage("Microsoft.Design", "CA1033:InterfaceMethodsShouldBeCallableByChildTypes", + Justification = "Cannot make this class sealed, it needs to have subclasses. But its subclasses are sealed themselves.")] + DynamicMetaObject IDynamicMetaObjectProvider.GetMetaObject(Expression parameter) + { + if (parameter == null) + { + throw new ArgumentNullException("parameter"); + } + + return new JsonValueDynamicMetaObject(parameter, this); + } +#endif + + /// <summary> + /// Resolves the specified object to an appropriate JsonValue instance. + /// </summary> + /// <param name="value">The object to resolve.</param> + /// <returns>A <see cref="JsonValue"/> instance resolved from the specified object.</returns> + internal static JsonValue ResolveObject(object value) + { + JsonPrimitive primitive; + + if (value == null) + { + return null; + } + + JsonValue jsonValue = value as JsonValue; + + if (jsonValue != null) + { + return jsonValue; + } + + if (JsonPrimitive.TryCreate(value, out primitive)) + { + return primitive; + } + + throw new ArgumentException(Properties.Resources.TypeNotSupported, "value"); + } + + /// <summary> + /// Determines whether an explicit cast to JsonValue is provided from the specified type. + /// </summary> + /// <param name="type">The type to check.</param> + /// <returns>true if an explicit cast exists for the specified type, false otherwise.</returns> + internal static bool IsSupportedExplicitCastType(Type type) + { + TypeCode typeCode = Type.GetTypeCode(type); + + switch (typeCode) + { + case TypeCode.Boolean: + case TypeCode.Byte: + case TypeCode.Char: + case TypeCode.DateTime: + case TypeCode.Decimal: + case TypeCode.Double: + case TypeCode.Int16: + case TypeCode.Int32: + case TypeCode.Int64: + case TypeCode.SByte: + case TypeCode.Single: + case TypeCode.String: + case TypeCode.UInt16: + case TypeCode.UInt32: + case TypeCode.UInt64: + return true; + + default: + return type == typeof(DateTimeOffset) || type == typeof(Guid) || type == typeof(Uri) || + type == typeof(List<object>) || type == typeof(Array) || type == typeof(object[]) || + type == typeof(Dictionary<string, object>); + } + } + + /// <summary> + /// Returns the value this object wraps (if any). + /// </summary> + /// <returns>The value wrapped by this instance or null if none.</returns> + internal virtual object Read() + { + return null; + } + + /// <summary> + /// Serializes this object into the specified <see cref="XmlDictionaryWriter"/> instance. + /// </summary> + /// <param name="jsonWriter">An <see cref="XmlDictionaryWriter"/> instance to serialize this instance into.</param> + internal virtual void Save(XmlDictionaryWriter jsonWriter) + { + if (jsonWriter == null) + { + throw new ArgumentNullException("jsonWriter"); + } + + Stack<JsonValue> objectStack = new Stack<JsonValue>(); + Stack<int> indexStack = new Stack<int>(); + int currentIndex = 0; + JsonValue currentValue = this; + + OnSaveStarted(); + + WriteAttributeString(jsonWriter); + + while (currentIndex < currentValue.Count || objectStack.Count > 0) + { + if (currentValue.Count > currentIndex) + { + JsonValue nextValue = currentValue.WriteStartElementAndGetNext(jsonWriter, currentIndex); + + if (JsonValue.IsJsonCollection(nextValue)) + { + nextValue.OnSaveStarted(); + nextValue.WriteAttributeString(jsonWriter); + + objectStack.Push(currentValue); + indexStack.Push(currentIndex); + currentValue = nextValue; + currentIndex = 0; + } + else + { + if (nextValue == null) + { + jsonWriter.WriteAttributeString(JXmlToJsonValueConverter.TypeAttributeName, JXmlToJsonValueConverter.NullAttributeValue); + } + else + { + nextValue.Save(jsonWriter); + } + + currentIndex++; + jsonWriter.WriteEndElement(); + } + } + else + { + if (objectStack.Count > 0) + { + currentValue.OnSaveEnded(); + jsonWriter.WriteEndElement(); + + currentValue = objectStack.Pop(); + currentIndex = indexStack.Pop() + 1; + } + } + } + + OnSaveEnded(); + } + + /// <summary> + /// Returns an enumerator which iterates through the values in this object. + /// </summary> + /// <returns>An <see cref="System.Collections.IEnumerator"/> which iterates through the values in this object.</returns> + /// <remarks>This method is the virtual version of the IEnumerator.GetEnumerator method and is provided to allow derived classes to implement the + /// appropriate version of the generic interface (enumerator of values or key/value pairs).</remarks> + [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", + Justification = "This method is a virtual version of the IEnumerable.GetEnumerator method.")] + [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", + Justification = "This class is a collection that is properly represented by the nested generic type.")] + protected virtual IEnumerator<KeyValuePair<string, JsonValue>> GetKeyValuePairEnumerator() + { + yield break; + } + + /// <summary> + /// Callback method called during Save operations to let the instance write the start element + /// and return the next element in the collection. + /// </summary> + /// <param name="jsonWriter">The JXML writer used to write JSON.</param> + /// <param name="index">The index within this collection.</param> + /// <returns>The next item in the collection, or null of there are no more items.</returns> + internal virtual JsonValue WriteStartElementAndGetNext(XmlDictionaryWriter jsonWriter, int index) + { + return null; + } + + /// <summary> + /// Callback method called to let an instance write the proper JXML attribute when saving this + /// instance. + /// </summary> + /// <param name="jsonWriter">The JXML writer used to write JSON.</param> + internal virtual void WriteAttributeString(XmlDictionaryWriter jsonWriter) + { + } + + /// <summary> + /// Callback method called when a Save operation is starting for this instance. + /// </summary> + protected virtual void OnSaveStarted() + { + } + + /// <summary> + /// Callback method called when a Save operation is finished for this instance. + /// </summary> + protected virtual void OnSaveEnded() + { + } + + /// <summary> + /// Called internally to raise the <see cref="Changing"/> event. + /// </summary> + /// <param name="sender">The object which caused the event to be raised.</param> + /// <param name="eventArgs">The arguments to the event.</param> + [SuppressMessage("Microsoft.Design", "CA1030:UseEventsWhereAppropriate", + Justification = "This is a helper function used to raise the event.")] + [SuppressMessage("Microsoft.Security", "CA2109:ReviewVisibleEventHandlers", + Justification = "This is not externally visible, since the constructor for this class is internal (cannot be directly derived) and all its subclasses are sealed.")] + protected void RaiseChangingEvent(object sender, JsonValueChangeEventArgs eventArgs) + { + EventHandler<JsonValueChangeEventArgs> changing = OnChanging; + if (changing != null) + { + changing(sender, eventArgs); + } + } + + /// <summary> + /// Called internally to raise the <see cref="Changed"/> event. + /// </summary> + /// <param name="sender">The object which caused the event to be raised.</param> + /// <param name="eventArgs">The arguments to the event.</param> + [SuppressMessage("Microsoft.Design", "CA1030:UseEventsWhereAppropriate", + Justification = "This is a helper function used to raise the event.")] + [SuppressMessage("Microsoft.Security", "CA2109:ReviewVisibleEventHandlers", + Justification = "This is not externally visible, since the constructor for this class is internal (cannot be directly derived) and all its subclasses are sealed.")] + protected void RaiseChangedEvent(object sender, JsonValueChangeEventArgs eventArgs) + { + EventHandler<JsonValueChangeEventArgs> changed = OnChanged; + if (changed != null) + { + changed(sender, eventArgs); + } + } + + private static bool IsJsonCollection(JsonValue value) + { + return value != null && (value.JsonType == JsonType.Array || value.JsonType == JsonType.Object); + } + + /// <summary> + /// Enables explicit casts from an instance of type <see cref="System.Json.JsonValue"/> to a <see cref="System.String"/> object. + /// </summary> + /// <param name="value">The instance of <see cref="System.Json.JsonValue"/> used to initialize the <see cref="System.String"/> object.</param> + /// <returns>The <see cref="System.String"/> initialized with the <see cref="System.Json.JsonValue"/> value specified or null if value is null.</returns> + public static explicit operator string(JsonValue value) + { + return CastValue<string>(value); + } + + /// <summary> + /// Enables explicit casts from an instance of type <see cref="System.Json.JsonValue"/> to a <see cref="System.Double"/> object. + /// </summary> + /// <param name="value">The instance of <see cref="System.Json.JsonValue"/> used to initialize the <see cref="System.Double"/> object.</param> + /// <returns>The <see cref="System.Double"/> initialized with the <see cref="System.Json.JsonValue"/> value specified.</returns> + public static explicit operator double(JsonValue value) + { + return CastValue<double>(value); + } + + /// <summary> + /// Enables explicit casts from an instance of type <see cref="System.Json.JsonValue"/> to a <see cref="System.Single"/> object. + /// </summary> + /// <param name="value">The instance of <see cref="System.Json.JsonValue"/> used to initialize the <see cref="System.Single"/> object.</param> + /// <returns>The <see cref="System.Single"/> initialized with the <see cref="System.Json.JsonValue"/> value specified.</returns> + public static explicit operator float(JsonValue value) + { + return CastValue<float>(value); + } + + /// <summary> + /// Enables explicit casts from an instance of type <see cref="System.Json.JsonValue"/> to a <see cref="System.Decimal"/> object. + /// </summary> + /// <param name="value">The instance of <see cref="System.Json.JsonValue"/> used to initialize the <see cref="System.Decimal"/> object.</param> + /// <returns>The <see cref="System.Decimal"/> initialized with the <see cref="System.Json.JsonValue"/> value specified.</returns> + public static explicit operator decimal(JsonValue value) + { + return CastValue<decimal>(value); + } + + /// <summary> + /// Enables explicit casts from an instance of type <see cref="System.Json.JsonValue"/> to a <see cref="System.Int64"/> object. + /// </summary> + /// <param name="value">The instance of <see cref="System.Json.JsonValue"/> used to initialize the <see cref="System.Int64"/> object.</param> + /// <returns>The <see cref="System.Int64"/> initialized with the <see cref="System.Json.JsonValue"/> value specified.</returns> + public static explicit operator long(JsonValue value) + { + return CastValue<long>(value); + } + + /// <summary> + /// Enables explicit casts from an instance of type <see cref="System.Json.JsonValue"/> to a <see cref="System.UInt64"/> object. + /// </summary> + /// <param name="value">The instance of <see cref="System.Json.JsonValue"/> used to initialize the <see cref="System.UInt64"/> object.</param> + /// <returns>The <see cref="System.UInt64"/> initialized with the <see cref="System.Json.JsonValue"/> value specified.</returns> + [CLSCompliant(false)] + public static explicit operator ulong(JsonValue value) + { + return CastValue<ulong>(value); + } + + /// <summary> + /// Enables explicit casts from an instance of type <see cref="System.Json.JsonValue"/> to a <see cref="System.Int32"/> object. + /// </summary> + /// <param name="value">The instance of <see cref="System.Json.JsonValue"/> used to initialize the <see cref="System.Int32"/> object.</param> + /// <returns>The <see cref="System.Int32"/> initialized with the <see cref="System.Json.JsonValue"/> value specified.</returns> + public static explicit operator int(JsonValue value) + { + return CastValue<int>(value); + } + + /// <summary> + /// Enables explicit casts from an instance of type <see cref="System.Json.JsonValue"/> to a <see cref="System.UInt32"/> object. + /// </summary> + /// <param name="value">The instance of <see cref="System.Json.JsonValue"/> used to initialize the <see cref="System.UInt32"/> object.</param> + /// <returns>The <see cref="System.UInt32"/> initialized with the <see cref="System.Json.JsonValue"/> value specified.</returns> + [CLSCompliant(false)] + public static explicit operator uint(JsonValue value) + { + return CastValue<uint>(value); + } + + /// <summary> + /// Enables explicit casts from an instance of type <see cref="System.Json.JsonValue"/> to a <see cref="System.Int16"/> object. + /// </summary> + /// <param name="value">The instance of <see cref="System.Json.JsonValue"/> used to initialize the <see cref="System.Int16"/> object.</param> + /// <returns>The <see cref="System.Int16"/> initialized with the <see cref="System.Json.JsonValue"/> value specified.</returns> + public static explicit operator short(JsonValue value) + { + return CastValue<short>(value); + } + + /// <summary> + /// Enables explicit casts from an instance of type <see cref="System.Json.JsonValue"/> to a <see cref="System.UInt16"/> object. + /// </summary> + /// <param name="value">The instance of <see cref="System.Json.JsonValue"/> used to initialize the <see cref="System.UInt16"/> object.</param> + /// <returns>The <see cref="System.UInt16"/> initialized with the <see cref="System.Json.JsonValue"/> value specified.</returns> + [CLSCompliant(false)] + public static explicit operator ushort(JsonValue value) + { + return CastValue<ushort>(value); + } + + /// <summary> + /// Enables explicit casts from an instance of type <see cref="System.Json.JsonValue"/> to a <see cref="System.SByte"/> object. + /// </summary> + /// <param name="value">The instance of <see cref="System.Json.JsonValue"/> used to initialize the <see cref="System.SByte"/> object.</param> + /// <returns>The <see cref="System.SByte"/> initialized with the <see cref="System.Json.JsonValue"/> value specified.</returns> + [CLSCompliant(false)] + public static explicit operator sbyte(JsonValue value) + { + return CastValue<sbyte>(value); + } + + /// <summary> + /// Enables explicit casts from an instance of type <see cref="System.Json.JsonValue"/> to a <see cref="System.Byte"/> object. + /// </summary> + /// <param name="value">The instance of <see cref="System.Json.JsonValue"/> used to initialize the <see cref="System.Byte"/> object.</param> + /// <returns>The <see cref="System.Byte"/> initialized with the <see cref="System.Json.JsonValue"/> value specified.</returns> + public static explicit operator byte(JsonValue value) + { + return CastValue<byte>(value); + } + + /// <summary> + /// Enables explicit casts from an instance of type <see cref="System.Json.JsonValue"/> to a <see cref="System.Uri"/> object. + /// </summary> + /// <param name="value">The instance of <see cref="System.Json.JsonValue"/> used to initialize the <see cref="System.Uri"/> object.</param> + /// <returns>The <see cref="System.Uri"/> initialized with the <see cref="System.Json.JsonValue"/> value specified or null if value is null.</returns> + public static explicit operator Uri(JsonValue value) + { + return CastValue<Uri>(value); + } + + /// <summary> + /// Enables explicit casts from an instance of type <see cref="System.Json.JsonValue"/> to a <see cref="System.Guid"/> object. + /// </summary> + /// <param name="value">The instance of <see cref="System.Json.JsonValue"/> used to initialize the <see cref="System.Guid"/> object.</param> + /// <returns>The <see cref="System.Guid"/> initialized with the <see cref="System.Json.JsonValue"/> value specified.</returns> + public static explicit operator Guid(JsonValue value) + { + return CastValue<Guid>(value); + } + + /// <summary> + /// Enables explicit casts from an instance of type <see cref="System.Json.JsonValue"/> to a <see cref="System.DateTime"/> object. + /// </summary> + /// <param name="value">The instance of <see cref="System.Json.JsonValue"/> used to initialize the <see cref="System.DateTime"/> object.</param> + /// <returns>The <see cref="System.DateTime"/> initialized with the <see cref="System.Json.JsonValue"/> value specified.</returns> + public static explicit operator DateTime(JsonValue value) + { + return CastValue<DateTime>(value); + } + + /// <summary> + /// Enables explicit casts from an instance of type <see cref="System.Json.JsonValue"/> to a <see cref="System.Char"/> object. + /// </summary> + /// <param name="value">The instance of <see cref="System.Json.JsonValue"/> used to initialize the <see cref="System.Char"/> object.</param> + /// <returns>The <see cref="System.Char"/> initialized with the <see cref="System.Json.JsonValue"/> value specified.</returns> + public static explicit operator char(JsonValue value) + { + return CastValue<char>(value); + } + + /// <summary> + /// Enables explicit casts from an instance of type <see cref="System.Json.JsonValue"/> to a <see cref="System.Boolean"/> object. + /// </summary> + /// <param name="value">The instance of <see cref="System.Json.JsonValue"/> used to initialize the <see cref="System.Boolean"/> object.</param> + /// <returns>The <see cref="System.Boolean"/> initialized with the <see cref="System.Json.JsonValue"/> value specified.</returns> + public static explicit operator bool(JsonValue value) + { + return CastValue<bool>(value); + } + + /// <summary> + /// Enables explicit casts from an instance of type <see cref="System.Json.JsonValue"/> to a <see cref="System.DateTimeOffset"/> object. + /// </summary> + /// <param name="value">The instance of <see cref="System.Json.JsonValue"/> used to initialize the <see cref="System.DateTimeOffset"/> object.</param> + /// <returns>The <see cref="System.DateTimeOffset"/> initialized with the <see cref="System.Json.JsonValue"/> value specified.</returns> + public static explicit operator DateTimeOffset(JsonValue value) + { + return CastValue<DateTimeOffset>(value); + } + + /// <summary> + /// Enables implicit casts from type <see cref="System.Boolean"/> to a <see cref="System.Json.JsonPrimitive"/>. + /// </summary> + /// <param name="value">The <see cref="System.Boolean"/> instance used to initialize the <see cref="System.Json.JsonPrimitive"/>.</param> + /// <returns>The <see cref="System.Json.JsonValue"/> initialized with the <see cref="System.Boolean"/> specified.</returns> + public static implicit operator JsonValue(bool value) + { + return new JsonPrimitive(value); + } + + /// <summary> + /// Enables implicit casts from type <see cref="System.Byte"/> to a <see cref="System.Json.JsonPrimitive"/>. + /// </summary> + /// <param name="value">The <see cref="System.Byte"/> instance used to initialize the <see cref="System.Json.JsonPrimitive"/>.</param> + /// <returns>The <see cref="System.Json.JsonValue"/> initialized with the <see cref="System.Byte"/> specified.</returns> + public static implicit operator JsonValue(byte value) + { + return new JsonPrimitive(value); + } + + /// <summary> + /// Enables implicit casts from type <see cref="System.Decimal"/> to a <see cref="System.Json.JsonPrimitive"/>. + /// </summary> + /// <param name="value">The <see cref="System.Decimal"/> instance used to initialize the <see cref="System.Json.JsonPrimitive"/>.</param> + /// <returns>The <see cref="System.Json.JsonValue"/> initialized with the <see cref="System.Decimal"/> specified.</returns> + public static implicit operator JsonValue(decimal value) + { + return new JsonPrimitive(value); + } + + /// <summary> + /// Enables implicit casts from type <see cref="System.Double"/> to a <see cref="System.Json.JsonPrimitive"/>. + /// </summary> + /// <param name="value">The <see cref="System.Double"/> instance used to initialize the <see cref="System.Json.JsonPrimitive"/>.</param> + /// <returns>The <see cref="System.Json.JsonValue"/> initialized with the <see cref="System.Double"/> specified.</returns> + public static implicit operator JsonValue(double value) + { + return new JsonPrimitive(value); + } + + /// <summary> + /// Enables implicit casts from type <see cref="System.Int16"/> to a <see cref="System.Json.JsonPrimitive"/>. + /// </summary> + /// <param name="value">The <see cref="System.Int16"/> instance used to initialize the <see cref="System.Json.JsonPrimitive"/>.</param> + /// <returns>The <see cref="System.Json.JsonValue"/> initialized with the <see cref="System.Int16"/> specified.</returns> + public static implicit operator JsonValue(short value) + { + return new JsonPrimitive(value); + } + + /// <summary> + /// Enables implicit casts from type <see cref="System.Int32"/> to a <see cref="System.Json.JsonPrimitive"/>. + /// </summary> + /// <param name="value">The <see cref="System.Int32"/> instance used to initialize the <see cref="System.Json.JsonPrimitive"/>.</param> + /// <returns>The <see cref="System.Json.JsonValue"/> initialized with the <see cref="System.Int32"/> specified.</returns> + public static implicit operator JsonValue(int value) + { + return new JsonPrimitive(value); + } + + /// <summary> + /// Enables implicit casts from type <see cref="System.Int64"/> to a <see cref="System.Json.JsonPrimitive"/>. + /// </summary> + /// <param name="value">The <see cref="System.Int64"/> instance used to initialize the <see cref="System.Json.JsonPrimitive"/>.</param> + /// <returns>The <see cref="System.Json.JsonValue"/> initialized with the <see cref="System.Int64"/> specified.</returns> + public static implicit operator JsonValue(long value) + { + return new JsonPrimitive(value); + } + + /// <summary> + /// Enables implicit casts from type <see cref="System.Single"/> to a <see cref="System.Json.JsonPrimitive"/>. + /// </summary> + /// <param name="value">The <see cref="System.Single"/> instance used to initialize the <see cref="System.Json.JsonPrimitive"/>.</param> + /// <returns>The <see cref="System.Json.JsonValue"/> initialized with the <see cref="System.Single"/> specified.</returns> + public static implicit operator JsonValue(float value) + { + return new JsonPrimitive(value); + } + + /// <summary> + /// Enables implicit casts from type <see cref="System.String"/> to a <see cref="System.Json.JsonPrimitive"/>. + /// </summary> + /// <param name="value">The <see cref="System.String"/> instance used to initialize the <see cref="System.Json.JsonPrimitive"/>.</param> + /// <returns>The <see cref="System.Json.JsonValue"/> initialized with the <see cref="System.String"/> specified, or null if the value is null.</returns> + [SuppressMessage("Microsoft.Design", "CA1057:StringUriOverloadsCallSystemUriOverloads", + Justification = "This operator does not intend to represent a Uri overload.")] + public static implicit operator JsonValue(string value) + { + return value == null ? null : new JsonPrimitive(value); + } + + /// <summary> + /// Enables implicit casts from type <see cref="System.Char"/> to a <see cref="System.Json.JsonPrimitive"/>. + /// </summary> + /// <param name="value">The <see cref="System.Char"/> instance used to initialize the <see cref="System.Json.JsonPrimitive"/>.</param> + /// <returns>The <see cref="System.Json.JsonValue"/> initialized with the <see cref="System.Char"/> specified.</returns> + public static implicit operator JsonValue(char value) + { + return new JsonPrimitive(value); + } + + /// <summary> + /// Enables implicit casts from type <see cref="System.DateTime"/> to a <see cref="System.Json.JsonPrimitive"/>. + /// </summary> + /// <param name="value">The <see cref="System.DateTime"/> instance used to initialize the <see cref="System.Json.JsonPrimitive"/>.</param> + /// <returns>The <see cref="System.Json.JsonValue"/> initialized with the <see cref="System.DateTime"/> specified.</returns> + public static implicit operator JsonValue(DateTime value) + { + return new JsonPrimitive(value); + } + + /// <summary> + /// Enables implicit casts from type <see cref="System.Guid"/> to a <see cref="System.Json.JsonPrimitive"/>. + /// </summary> + /// <param name="value">The <see cref="System.Guid"/> instance used to initialize the <see cref="System.Json.JsonPrimitive"/>.</param> + /// <returns>The <see cref="System.Json.JsonValue"/> initialized with the <see cref="System.Guid"/> specified.</returns> + public static implicit operator JsonValue(Guid value) + { + return new JsonPrimitive(value); + } + + /// <summary> + /// Enables implicit casts from type <see cref="System.Uri"/> to a <see cref="System.Json.JsonPrimitive"/>. + /// </summary> + /// <param name="value">The <see cref="System.Uri"/> instance used to initialize the <see cref="System.Json.JsonPrimitive"/>.</param> + /// <returns>The <see cref="System.Json.JsonValue"/> initialized with the <see cref="System.Uri"/> specified, or null if the value is null.</returns> + public static implicit operator JsonValue(Uri value) + { + return value == null ? null : new JsonPrimitive(value); + } + + /// <summary> + /// Enables implicit casts from type <see cref="System.SByte"/> to a <see cref="System.Json.JsonPrimitive"/>. + /// </summary> + /// <param name="value">The <see cref="System.SByte"/> instance used to initialize the <see cref="System.Json.JsonPrimitive"/>.</param> + /// <returns>The <see cref="System.Json.JsonValue"/> initialized with the <see cref="System.SByte"/> specified.</returns> + [CLSCompliant(false)] + public static implicit operator JsonValue(sbyte value) + { + return new JsonPrimitive(value); + } + + /// <summary> + /// Enables implicit casts from type <see cref="System.UInt16"/> to a <see cref="System.Json.JsonPrimitive"/>. + /// </summary> + /// <param name="value">The <see cref="System.UInt16"/> instance used to initialize the <see cref="System.Json.JsonPrimitive"/>.</param> + /// <returns>The <see cref="System.Json.JsonValue"/> initialized with the <see cref="System.UInt16"/> specified.</returns> + [CLSCompliant(false)] + public static implicit operator JsonValue(ushort value) + { + return new JsonPrimitive(value); + } + + /// <summary> + /// Enables implicit casts from type <see cref="System.UInt32"/> to a <see cref="System.Json.JsonPrimitive"/>. + /// </summary> + /// <param name="value">The <see cref="System.UInt32"/> instance used to initialize the <see cref="System.Json.JsonPrimitive"/>.</param> + /// <returns>The <see cref="System.Json.JsonValue"/> initialized with the <see cref="System.UInt32"/> specified.</returns> + [CLSCompliant(false)] + public static implicit operator JsonValue(uint value) + { + return new JsonPrimitive(value); + } + + /// <summary> + /// Enables implicit casts from type <see cref="System.UInt64"/> to a <see cref="System.Json.JsonPrimitive"/>. + /// </summary> + /// <param name="value">The <see cref="System.UInt64"/> instance used to initialize the <see cref="System.Json.JsonPrimitive"/>.</param> + /// <returns>The <see cref="System.Json.JsonValue"/> initialized with the <see cref="System.UInt64"/> specified.</returns> + [CLSCompliant(false)] + public static implicit operator JsonValue(ulong value) + { + return new JsonPrimitive(value); + } + + /// <summary> + /// Enables implicit casts from type <see cref="System.DateTimeOffset"/> to a <see cref="System.Json.JsonPrimitive"/>. + /// </summary> + /// <param name="value">The <see cref="System.DateTimeOffset"/> instance used to initialize the <see cref="System.Json.JsonPrimitive"/>.</param> + /// <returns>The <see cref="System.Json.JsonValue"/> initialized with the <see cref="System.DateTimeOffset"/> specified.</returns> + public static implicit operator JsonValue(DateTimeOffset value) + { + return new JsonPrimitive(value); + } + } +} diff --git a/mcs/class/System.Json/System.Json/JsonValueChange.cs b/mcs/class/System.Json/System.Json/JsonValueChange.cs new file mode 100644 index 00000000000..2182e622c77 --- /dev/null +++ b/mcs/class/System.Json/System.Json/JsonValueChange.cs @@ -0,0 +1,30 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. + +namespace System.Json +{ + /// <summary> + /// Specifies the event type when an event is raised for a <see cref="System.Json.JsonValue"/>. + /// </summary> + public enum JsonValueChange + { + /// <summary> + /// An element has been or will be added to the collection. + /// </summary> + Add, + + /// <summary> + /// An element has been or will be removed from the collection. + /// </summary> + Remove, + + /// <summary> + /// An element has been or will be replaced in the collection. Used on indexers. + /// </summary> + Replace, + + /// <summary> + /// All elements of the collection have been or will be removed. + /// </summary> + Clear, + } +} diff --git a/mcs/class/System.Json/System.Json/JsonValueChangeEventArgs.cs b/mcs/class/System.Json/System.Json/JsonValueChangeEventArgs.cs new file mode 100644 index 00000000000..cf7097b2047 --- /dev/null +++ b/mcs/class/System.Json/System.Json/JsonValueChangeEventArgs.cs @@ -0,0 +1,98 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. + +namespace System.Json +{ + /// <summary> + /// Provide data for the <see cref="System.Json.JsonValue.Changing"/> and <see cref="System.Json.JsonValue.Changed"/> events. + /// </summary> + public class JsonValueChangeEventArgs : EventArgs + { + private JsonValue child; + private JsonValueChange change; + private int index; + private string key; + + /// <summary> + /// Initializes a new instance of the <see cref="System.Json.JsonValueChangeEventArgs"/> class for + /// changes in a <see cref="System.Json.JsonArray"/>. + /// </summary> + /// <param name="child">The <see cref="System.Json.JsonValue"/> instance which will be or has been modified.</param> + /// <param name="change">The type of change of the <see cref="System.Json.JsonValue"/> event.</param> + /// <param name="index">The index of the element being changed in a <see cref="System.Json.JsonArray"/>.</param> + public JsonValueChangeEventArgs(JsonValue child, JsonValueChange change, int index) + { + if (index < 0) + { + throw new ArgumentOutOfRangeException("index", RS.Format(Properties.Resources.ArgumentMustBeGreaterThanOrEqualTo, index, 0)); + } + + this.child = child; + this.change = change; + this.index = index; + } + + /// <summary> + /// Initializes a new instance of the <see cref="System.Json.JsonValueChangeEventArgs"/> class for + /// changes in a <see cref="System.Json.JsonObject"/>. + /// </summary> + /// <param name="child">The <see cref="System.Json.JsonValue"/> instance which will be or has been modified.</param> + /// <param name="change">The type of change of the <see cref="System.Json.JsonValue"/> event.</param> + /// <param name="key">The key of the element being changed in a <see cref="System.Json.JsonObject"/>.</param> + public JsonValueChangeEventArgs(JsonValue child, JsonValueChange change, string key) + { + if (change != JsonValueChange.Clear) + { + if (key == null) + { + throw new ArgumentNullException("key"); + } + } + + this.child = child; + this.change = change; + index = -1; + this.key = key; + } + + /// <summary> + /// Gets the child which will be or has been modified. + /// </summary> + /// <remarks><p>This property is <code>null</code> for <see cref="System.Json.JsonValueChange.Clear"/> event types + /// raised by <see cref="System.Json.JsonValue"/> instances.</p> + /// <p>For <see cref="System.Json.JsonValueChange">Replace</see> events, this property contains the new value in + /// the <see cref="System.Json.JsonValue.Changing"/> event, and the old value (the one being replaced) in the + /// <see cref="System.Json.JsonValue.Changed"/> event.</p></remarks> + public JsonValue Child + { + get { return child; } + } + + /// <summary> + /// Gets the type of change. + /// </summary> + public JsonValueChange Change + { + get { return change; } + } + + /// <summary> + /// Gets the index in the <see cref="System.Json.JsonArray"/> where the change happened, or + /// <code>-1</code> if the change happened in a <see cref="System.Json.JsonValue"/> of a different type. + /// </summary> + public int Index + { + get { return index; } + } + + /// <summary> + /// Gets the key in the <see cref="System.Json.JsonObject"/> where the change happened, or + /// <code>null</code> if the change happened in a <see cref="System.Json.JsonValue"/> of a different type. + /// </summary> + /// <remarks>This property can also be <code>null</code> if the event type is + /// <see cref="System.Json.JsonValueChange">Clear</see>.</remarks> + public string Key + { + get { return key; } + } + } +} diff --git a/mcs/class/System.Json/System.Json/JsonValueDynamicMetaObject.cs b/mcs/class/System.Json/System.Json/JsonValueDynamicMetaObject.cs new file mode 100644 index 00000000000..f38f98fc4f2 --- /dev/null +++ b/mcs/class/System.Json/System.Json/JsonValueDynamicMetaObject.cs @@ -0,0 +1,384 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +#if FEATURE_DYNAMIC +using System.Collections.Generic; +using System.Dynamic; +using System.Linq.Expressions; +using System.Reflection; +using System.Runtime.Serialization.Json; + +namespace System.Json +{ + /// <summary> + /// This class provides dynamic behavior support for the JsonValue types. + /// </summary> + internal class JsonValueDynamicMetaObject : DynamicMetaObject + { + private static readonly MethodInfo _getValueByIndexMethodInfo = typeof(JsonValue).GetMethod("GetValue", new Type[] { typeof(int) }); + private static readonly MethodInfo _getValueByKeyMethodInfo = typeof(JsonValue).GetMethod("GetValue", new Type[] { typeof(string) }); + private static readonly MethodInfo _setValueByIndexMethodInfo = typeof(JsonValue).GetMethod("SetValue", new Type[] { typeof(int), typeof(object) }); + private static readonly MethodInfo _setValueByKeyMethodInfo = typeof(JsonValue).GetMethod("SetValue", new Type[] { typeof(string), typeof(object) }); + private static readonly MethodInfo _castValueMethodInfo = typeof(JsonValue).GetMethod("CastValue", new Type[] { typeof(JsonValue) }); + private static readonly MethodInfo _changeTypeMethodInfo = typeof(Convert).GetMethod("ChangeType", new Type[] { typeof(object), typeof(Type) }); + + /// <summary> + /// Class constructor. + /// </summary> + /// <param name="parameter">The expression representing this <see cref="DynamicMetaObject"/> during the dynamic binding process.</param> + /// <param name="value">The runtime value represented by the <see cref="DynamicMetaObject"/>.</param> + internal JsonValueDynamicMetaObject(Expression parameter, JsonValue value) + : base(parameter, BindingRestrictions.Empty, value) + { + } + + /// <summary> + /// Gets the default binding restrictions for this type. + /// </summary> + private BindingRestrictions DefaultRestrictions + { + get { return BindingRestrictions.GetTypeRestriction(Expression, LimitType); } + } + + /// <summary> + /// Implements dynamic cast for JsonValue types. + /// </summary> + /// <param name="binder">An instance of the <see cref="ConvertBinder"/> that represents the details of the dynamic operation.</param> + /// <returns>The new <see cref="DynamicMetaObject"/> representing the result of the binding.</returns> + public override DynamicMetaObject BindConvert(ConvertBinder binder) + { + if (binder == null) + { + throw new ArgumentNullException("binder"); + } + + Expression expression = Expression; + + bool implicitCastSupported = + binder.Type.IsAssignableFrom(LimitType) || + binder.Type == typeof(IEnumerable<KeyValuePair<string, JsonValue>>) || + binder.Type == typeof(IDynamicMetaObjectProvider) || + binder.Type == typeof(object); + + if (!implicitCastSupported) + { + if (JsonValue.IsSupportedExplicitCastType(binder.Type)) + { + Expression instance = Expression.Convert(Expression, LimitType); + expression = Expression.Call(_castValueMethodInfo.MakeGenericMethod(binder.Type), new Expression[] { instance }); + } + else + { + string exceptionMessage = RS.Format(Properties.Resources.CannotCastJsonValue, LimitType.FullName, binder.Type.FullName); + expression = Expression.Throw(Expression.Constant(new InvalidCastException(exceptionMessage)), typeof(object)); + } + } + + expression = Expression.Convert(expression, binder.Type); + + return new DynamicMetaObject(expression, DefaultRestrictions); + } + + /// <summary> + /// Implements setter for dynamic indexer by index (JsonArray) + /// </summary> + /// <param name="binder">An instance of the <see cref="GetIndexBinder"/> that represents the details of the dynamic operation.</param> + /// <param name="indexes">An array of <see cref="DynamicMetaObject"/> instances - indexes for the get index operation.</param> + /// <returns>The new <see cref="DynamicMetaObject"/> representing the result of the binding.</returns> + public override DynamicMetaObject BindGetIndex(GetIndexBinder binder, DynamicMetaObject[] indexes) + { + if (binder == null) + { + throw new ArgumentNullException("binder"); + } + + if (indexes == null) + { + throw new ArgumentNullException("indexes"); + } + + Expression indexExpression; + if (!JsonValueDynamicMetaObject.TryGetIndexExpression(indexes, out indexExpression)) + { + return new DynamicMetaObject(indexExpression, DefaultRestrictions); + } + + MethodInfo methodInfo = indexExpression.Type == typeof(string) ? _getValueByKeyMethodInfo : _getValueByIndexMethodInfo; + Expression[] args = new Expression[] { indexExpression }; + + return GetMethodMetaObject(methodInfo, args); + } + + /// <summary> + /// Implements getter for dynamic indexer by index (JsonArray). + /// </summary> + /// <param name="binder">An instance of the <see cref="SetIndexBinder"/> that represents the details of the dynamic operation.</param> + /// <param name="indexes">An array of <see cref="DynamicMetaObject"/> instances - indexes for the set index operation.</param> + /// <param name="value">The <see cref="DynamicMetaObject"/> representing the value for the set index operation.</param> + /// <returns>The new <see cref="DynamicMetaObject"/> representing the result of the binding.</returns> + public override DynamicMetaObject BindSetIndex(SetIndexBinder binder, DynamicMetaObject[] indexes, DynamicMetaObject value) + { + if (binder == null) + { + throw new ArgumentNullException("binder"); + } + + if (indexes == null) + { + throw new ArgumentNullException("indexes"); + } + + if (value == null) + { + throw new ArgumentNullException("value"); + } + + Expression indexExpression; + if (!JsonValueDynamicMetaObject.TryGetIndexExpression(indexes, out indexExpression)) + { + return new DynamicMetaObject(indexExpression, DefaultRestrictions); + } + + MethodInfo methodInfo = indexExpression.Type == typeof(string) ? _setValueByKeyMethodInfo : _setValueByIndexMethodInfo; + Expression[] args = new Expression[] { indexExpression, Expression.Convert(value.Expression, typeof(object)) }; + + return GetMethodMetaObject(methodInfo, args); + } + + /// <summary> + /// Implements getter for dynamic indexer by key (JsonObject). + /// </summary> + /// <param name="binder">An instance of the <see cref="GetMemberBinder"/> that represents the details of the dynamic operation.</param> + /// <returns>The new <see cref="DynamicMetaObject"/> representing the result of the binding.</returns> + public override DynamicMetaObject BindGetMember(GetMemberBinder binder) + { + if (binder == null) + { + throw new ArgumentNullException("binder"); + } + + PropertyInfo propInfo = LimitType.GetProperty(binder.Name, BindingFlags.Instance | BindingFlags.Public); + + if (propInfo != null) + { + return base.BindGetMember(binder); + } + + Expression[] args = new Expression[] { Expression.Constant(binder.Name) }; + + return GetMethodMetaObject(_getValueByKeyMethodInfo, args); + } + + /// <summary> + /// Implements setter for dynamic indexer by key (JsonObject). + /// </summary> + /// <param name="binder">An instance of the <see cref="SetMemberBinder"/> that represents the details of the dynamic operation.</param> + /// <param name="value">The <see cref="DynamicMetaObject"/> representing the value for the set member operation.</param> + /// <returns>The new <see cref="DynamicMetaObject"/> representing the result of the binding.</returns> + public override DynamicMetaObject BindSetMember(SetMemberBinder binder, DynamicMetaObject value) + { + if (binder == null) + { + throw new ArgumentNullException("binder"); + } + + if (value == null) + { + throw new ArgumentNullException("value"); + } + + Expression[] args = new Expression[] { Expression.Constant(binder.Name), Expression.Convert(value.Expression, typeof(object)) }; + + return GetMethodMetaObject(_setValueByKeyMethodInfo, args); + } + + /// <summary> + /// Performs the binding of the dynamic invoke member operation. + /// Implemented to support extension methods defined in <see cref="JsonValueExtensions"/> type. + /// </summary> + /// <param name="binder">An instance of the InvokeMemberBinder that represents the details of the dynamic operation.</param> + /// <param name="args">An array of DynamicMetaObject instances - arguments to the invoke member operation.</param> + /// <returns>The new DynamicMetaObject representing the result of the binding.</returns> + public override DynamicMetaObject BindInvokeMember(InvokeMemberBinder binder, DynamicMetaObject[] args) + { + if (binder == null) + { + throw new ArgumentNullException("binder"); + } + + if (args == null) + { + throw new ArgumentNullException("args"); + } + + List<Type> argTypeList = new List<Type>(); + + for (int idx = 0; idx < args.Length; idx++) + { + argTypeList.Add(args[idx].LimitType); + } + + MethodInfo methodInfo = Value.GetType().GetMethod(binder.Name, argTypeList.ToArray()); + + if (methodInfo == null) + { + argTypeList.Insert(0, typeof(JsonValue)); + + Type[] argTypes = argTypeList.ToArray(); + + methodInfo = JsonValueDynamicMetaObject.GetExtensionMethod(typeof(JsonValueExtensions), binder.Name, argTypes); + + if (methodInfo != null) + { + Expression thisInstance = Expression.Convert(Expression, LimitType); + Expression[] argsExpression = new Expression[argTypes.Length]; + + argsExpression[0] = thisInstance; + for (int i = 0; i < args.Length; i++) + { + argsExpression[i + 1] = args[i].Expression; + } + + Expression callExpression = Expression.Call(methodInfo, argsExpression); + + if (methodInfo.ReturnType == typeof(void)) + { + callExpression = Expression.Block(callExpression, Expression.Default(binder.ReturnType)); + } + else + { + callExpression = Expression.Convert(Expression.Call(methodInfo, argsExpression), binder.ReturnType); + } + + return new DynamicMetaObject(callExpression, DefaultRestrictions); + } + } + + return base.BindInvokeMember(binder, args); + } + + /// <summary> + /// Returns the enumeration of all dynamic member names. + /// </summary> + /// <returns>An <see cref="IEnumerable{T}"/> of string reprenseting the dynamic member names.</returns> + public override IEnumerable<string> GetDynamicMemberNames() + { + JsonValue jsonValue = Value as JsonValue; + + if (jsonValue != null) + { + List<string> names = new List<string>(); + + foreach (KeyValuePair<string, JsonValue> pair in jsonValue) + { + names.Add(pair.Key); + } + + return names; + } + + return base.GetDynamicMemberNames(); + } + + /// <summary> + /// Gets a <see cref="MethodInfo"/> instance for the specified method name in the specified type. + /// </summary> + /// <param name="extensionProviderType">The extension provider type.</param> + /// <param name="methodName">The name of the method to get the info for.</param> + /// <param name="argTypes">The types of the method arguments.</param> + /// <returns>A <see cref="MethodInfo"/>instance or null if the method cannot be resolved.</returns> + private static MethodInfo GetExtensionMethod(Type extensionProviderType, string methodName, Type[] argTypes) + { + MethodInfo methodInfo = null; + MethodInfo[] methods = extensionProviderType.GetMethods(); + + foreach (MethodInfo info in methods) + { + if (info.Name == methodName) + { + methodInfo = info; + + if (!info.IsGenericMethodDefinition) + { + bool paramsMatch = true; + ParameterInfo[] args = methodInfo.GetParameters(); + + if (args.Length == argTypes.Length) + { + for (int idx = 0; idx < args.Length; idx++) + { + if (!args[idx].ParameterType.IsAssignableFrom(argTypes[idx])) + { + paramsMatch = false; + break; + } + } + + if (paramsMatch) + { + break; + } + } + } + } + } + + return methodInfo; + } + + /// <summary> + /// Attempts to get an expression for an index parameter. + /// </summary> + /// <param name="indexes">The operation indexes parameter.</param> + /// <param name="expression">A <see cref="Expression"/> to be initialized to the index expression if the operation is successful, otherwise an error expression.</param> + /// <returns>true the operation is successful, false otherwise.</returns> + private static bool TryGetIndexExpression(DynamicMetaObject[] indexes, out Expression expression) + { + if (indexes.Length == 1 && indexes[0] != null && indexes[0].Value != null) + { + DynamicMetaObject index = indexes[0]; + Type indexType = indexes[0].Value.GetType(); + + switch (Type.GetTypeCode(indexType)) + { + case TypeCode.Char: + case TypeCode.Int16: + case TypeCode.UInt16: + case TypeCode.Byte: + case TypeCode.SByte: + Expression argExp = Expression.Convert(index.Expression, typeof(object)); + Expression typeExp = Expression.Constant(typeof(int)); + expression = Expression.Convert(Expression.Call(_changeTypeMethodInfo, new Expression[] { argExp, typeExp }), typeof(int)); + return true; + + case TypeCode.Int32: + case TypeCode.String: + expression = index.Expression; + return true; + } + + expression = Expression.Throw(Expression.Constant(new ArgumentException(RS.Format(Properties.Resources.InvalidIndexType, indexType))), typeof(object)); + return false; + } + + expression = Expression.Throw(Expression.Constant(new ArgumentException(Properties.Resources.NonSingleNonNullIndexNotSupported)), typeof(object)); + return false; + } + + /// <summary> + /// Gets a <see cref="DynamicMetaObject"/> for a method call. + /// </summary> + /// <param name="methodInfo">Info for the method to be performed.</param> + /// <param name="args">expression array representing the method arguments</param> + /// <returns>A meta object for the method call.</returns> + private DynamicMetaObject GetMethodMetaObject(MethodInfo methodInfo, Expression[] args) + { + Expression instance = Expression.Convert(Expression, LimitType); + Expression methodCall = Expression.Call(instance, methodInfo, args); + BindingRestrictions restrictions = DefaultRestrictions; + + DynamicMetaObject metaObj = new DynamicMetaObject(methodCall, restrictions); + + return metaObj; + } + } +} +#endif diff --git a/mcs/class/System.Json/System.Json/JsonValueLinqExtensions.cs b/mcs/class/System.Json/System.Json/JsonValueLinqExtensions.cs new file mode 100644 index 00000000000..5516a037371 --- /dev/null +++ b/mcs/class/System.Json/System.Json/JsonValueLinqExtensions.cs @@ -0,0 +1,37 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; + +namespace System.Json +{ + /// <summary> + /// This class extends the funcionality of the <see cref="JsonValue"/> type for better Linq support . + /// </summary> + [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", Justification = "Linq is a technical name.")] + [EditorBrowsable(EditorBrowsableState.Never)] + public static class JsonValueLinqExtensions + { + /// <summary> + /// Extension method for creating a <see cref="JsonValue"/> from an <see cref="IEnumerable{T}"/> collection of <see cref="JsonValue"/> types. + /// </summary> + /// <param name="items">The enumerable instance.</param> + /// <returns>A <see cref="JsonArray"/> created from the specified items.</returns> + public static JsonArray ToJsonArray(this IEnumerable<JsonValue> items) + { + return new JsonArray(items); + } + + /// <summary> + /// Extension method for creating a <see cref="JsonValue"/> from an <see cref="IEnumerable{T}"/> collection of <see cref="KeyValuePair{K,V}"/> of <see cref="String"/> and <see cref="JsonValue"/> types. + /// </summary> + /// <param name="items">The enumerable instance.</param> + /// <returns>A <see cref="JsonValue"/> created from the specified items.</returns> + [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "JsonValue implements the nested type in param.")] + public static JsonObject ToJsonObject(this IEnumerable<KeyValuePair<string, JsonValue>> items) + { + return new JsonObject(items); + } + } +} diff --git a/mcs/class/System.Json/System.Json/NGenWrapper.cs b/mcs/class/System.Json/System.Json/NGenWrapper.cs new file mode 100644 index 00000000000..4a9e7b5c2a3 --- /dev/null +++ b/mcs/class/System.Json/System.Json/NGenWrapper.cs @@ -0,0 +1,48 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. + +namespace System.Json +{ + /// <summary> + /// Struct that wraps values which cause JIT compilation at runtime. + /// This Struct is added to solve the FxCop warning CA908 in JsonObject.cs. + /// </summary> + /// <typeparam name="T">Wrapped type.</typeparam> + internal struct NGenWrapper<T> + { + /// <summary> + /// Value of type T which represents the actual data which is currently in hold. + /// </summary> + public T Value; + + /// <summary> + /// Creates an instance of the <see cref="System.Json.NGenWrapper{T}"/> class + /// </summary> + /// <param name="value">The wrapped object of T</param> + public NGenWrapper(T value) + { + Value = value; + } + + /// <summary> + /// Cast operator from <see cref="System.Json.NGenWrapper{T}"/> to <typeparamref name="T"/> + /// </summary> + /// <param name="value">Object in type <see cref="System.Json.NGenWrapper{T}"/></param> + /// <returns>Object in type <typeparamref name="T">The wrapped element type</typeparamref></returns> + /// <typeparamref name="T">The wrapped element type</typeparamref> + public static implicit operator T(NGenWrapper<T> value) + { + return value.Value; + } + + /// <summary> + /// Cast operator from <typeparamref name="T"/> to <see cref="System.Json.NGenWrapper{T}"/> + /// </summary> + /// <param name="value">Object in type <typeparamref name="T"/></param> + /// <returns>Object in type <see cref="System.Json.NGenWrapper{T}"/></returns> + /// <typeparamref name="T">The wrapped element type</typeparamref> + public static implicit operator NGenWrapper<T>(T value) + { + return new NGenWrapper<T>(value); + } + } +} diff --git a/mcs/class/System.Json/System.Json/Properties/AssemblyInfo.cs b/mcs/class/System.Json/System.Json/Properties/AssemblyInfo.cs new file mode 100644 index 00000000000..f413e072853 --- /dev/null +++ b/mcs/class/System.Json/System.Json/Properties/AssemblyInfo.cs @@ -0,0 +1,12 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. + +using System.Reflection; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. + +[assembly: AssemblyTitle("System.Json")] +[assembly: AssemblyDescription("")] +[assembly: Guid("6fd72360-ebfc-4097-96fa-2ee418c04f7b")] diff --git a/mcs/class/System.Json/System.Json/Properties/Resources.Designer.cs b/mcs/class/System.Json/System.Json/Properties/Resources.Designer.cs new file mode 100644 index 00000000000..5a0e1206de3 --- /dev/null +++ b/mcs/class/System.Json/System.Json/Properties/Resources.Designer.cs @@ -0,0 +1,216 @@ +//------------------------------------------------------------------------------ +// <auto-generated> +// This code was generated by a tool. +// Runtime Version:4.0.30319.239 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// </auto-generated> +//------------------------------------------------------------------------------ + +namespace System.Json.Properties { + using System; + + + /// <summary> + /// A strongly-typed resource class, for looking up localized strings, etc. + /// </summary> + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// <summary> + /// Returns the cached ResourceManager instance used by this class. + /// </summary> + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("System.Json.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// <summary> + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// </summary> + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// <summary> + /// Looks up a localized string similar to The argument '{0}' must be greater than or equal to {1}.. + /// </summary> + internal static string ArgumentMustBeGreaterThanOrEqualTo { + get { + return ResourceManager.GetString("ArgumentMustBeGreaterThanOrEqualTo", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Unable to cast object of type '{0}' to type '{1}'.. + /// </summary> + internal static string CannotCastJsonValue { + get { + return ResourceManager.GetString("CannotCastJsonValue", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to CannotReadAsType=Cannot read '{0}' as '{1}' type.. + /// </summary> + internal static string CannotReadAsType { + get { + return ResourceManager.GetString("CannotReadAsType", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Cannot read JsonPrimitive value '{0}' as '{1}'.. + /// </summary> + internal static string CannotReadPrimitiveAsType { + get { + return ResourceManager.GetString("CannotReadPrimitiveAsType", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to '{0}' does not contain a definition for property '{1}'.. + /// </summary> + internal static string DynamicPropertyNotDefined { + get { + return ResourceManager.GetString("DynamicPropertyNotDefined", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to The input source is not correctly formatted.. + /// </summary> + internal static string IncorrectJsonFormat { + get { + return ResourceManager.GetString("IncorrectJsonFormat", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to '{0}' type indexer is not supported on JsonValue of 'JsonType.{1}' type.. + /// </summary> + internal static string IndexerNotSupportedOnJsonType { + get { + return ResourceManager.GetString("IndexerNotSupportedOnJsonType", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Cannot convert null to '{0}' because it is a non-nullable value type.. + /// </summary> + internal static string InvalidCastNonNullable { + get { + return ResourceManager.GetString("InvalidCastNonNullable", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Cannot cast JsonPrimitive value '{0}' as '{1}'. It is not in a valid date format.. + /// </summary> + internal static string InvalidDateFormat { + get { + return ResourceManager.GetString("InvalidDateFormat", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Invalid '{0}' index type; only 'System.String' and non-negative 'System.Int32' types are supported.. + /// </summary> + internal static string InvalidIndexType { + get { + return ResourceManager.GetString("InvalidIndexType", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Invalid JSON primitive: {0}.. + /// </summary> + internal static string InvalidJsonPrimitive { + get { + return ResourceManager.GetString("InvalidJsonPrimitive", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Cannot cast '{0}' value '{1}.{2}' as a type of '{3}'. The provided string is not a valid relative or absolute '{3}'.. + /// </summary> + internal static string InvalidUriFormat { + get { + return ResourceManager.GetString("InvalidUriFormat", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to An empty string cannot be parsed as JSON.. + /// </summary> + internal static string JsonStringCannotBeEmpty { + get { + return ResourceManager.GetString("JsonStringCannotBeEmpty", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Null index or multidimensional indexing is not supported by this indexer; use 'System.Int32' or 'System.String' for array and object indexing respectively.. + /// </summary> + internal static string NonSingleNonNullIndexNotSupported { + get { + return ResourceManager.GetString("NonSingleNonNullIndexNotSupported", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Cannot cast JsonPrimitive value '{0}' as '{1}'. The value is either too large or too small for the specified CLR type.. + /// </summary> + internal static string OverflowReadAs { + get { + return ResourceManager.GetString("OverflowReadAs", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Object type not supported.. + /// </summary> + internal static string TypeNotSupported { + get { + return ResourceManager.GetString("TypeNotSupported", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Operation not supported on JsonValue instances of 'JsonType.Default' type.. + /// </summary> + internal static string UseOfDefaultNotAllowed { + get { + return ResourceManager.GetString("UseOfDefaultNotAllowed", resourceCulture); + } + } + } +} diff --git a/mcs/class/System.Json/System.Json/Properties/Resources.resx b/mcs/class/System.Json/System.Json/Properties/Resources.resx new file mode 100644 index 00000000000..02fd621dcef --- /dev/null +++ b/mcs/class/System.Json/System.Json/Properties/Resources.resx @@ -0,0 +1,171 @@ +<?xml version="1.0" encoding="utf-8"?> +<root> + <!-- + Microsoft ResX Schema + + Version 2.0 + + The primary goals of this format is to allow a simple XML format + that is mostly human readable. The generation and parsing of the + various data types are done through the TypeConverter classes + associated with the data types. + + Example: + + ... ado.net/XML headers & schema ... + <resheader name="resmimetype">text/microsoft-resx</resheader> + <resheader name="version">2.0</resheader> + <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader> + <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader> + <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data> + <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data> + <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64"> + <value>[base64 mime encoded serialized .NET Framework object]</value> + </data> + <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64"> + <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value> + <comment>This is a comment</comment> + </data> + + There are any number of "resheader" rows that contain simple + name/value pairs. + + Each data row contains a name, and value. The row also contains a + type or mimetype. Type corresponds to a .NET class that support + text/value conversion through the TypeConverter architecture. + Classes that don't support this are serialized and stored with the + mimetype set. + + The mimetype is used for serialized objects, and tells the + ResXResourceReader how to depersist the object. This is currently not + extensible. For a given mimetype the value must be set accordingly: + + Note - application/x-microsoft.net.object.binary.base64 is the format + that the ResXResourceWriter will generate, however the reader can + read any of the formats listed below. + + mimetype: application/x-microsoft.net.object.binary.base64 + value : The object must be serialized with + : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter + : and then encoded with base64 encoding. + + mimetype: application/x-microsoft.net.object.soap.base64 + value : The object must be serialized with + : System.Runtime.Serialization.Formatters.Soap.SoapFormatter + : and then encoded with base64 encoding. + + mimetype: application/x-microsoft.net.object.bytearray.base64 + value : The object must be serialized into a byte array + : using a System.ComponentModel.TypeConverter + : and then encoded with base64 encoding. + --> + <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"> + <xsd:import namespace="http://www.w3.org/XML/1998/namespace" /> + <xsd:element name="root" msdata:IsDataSet="true"> + <xsd:complexType> + <xsd:choice maxOccurs="unbounded"> + <xsd:element name="metadata"> + <xsd:complexType> + <xsd:sequence> + <xsd:element name="value" type="xsd:string" minOccurs="0" /> + </xsd:sequence> + <xsd:attribute name="name" use="required" type="xsd:string" /> + <xsd:attribute name="type" type="xsd:string" /> + <xsd:attribute name="mimetype" type="xsd:string" /> + <xsd:attribute ref="xml:space" /> + </xsd:complexType> + </xsd:element> + <xsd:element name="assembly"> + <xsd:complexType> + <xsd:attribute name="alias" type="xsd:string" /> + <xsd:attribute name="name" type="xsd:string" /> + </xsd:complexType> + </xsd:element> + <xsd:element name="data"> + <xsd:complexType> + <xsd:sequence> + <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /> + <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" /> + </xsd:sequence> + <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" /> + <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" /> + <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" /> + <xsd:attribute ref="xml:space" /> + </xsd:complexType> + </xsd:element> + <xsd:element name="resheader"> + <xsd:complexType> + <xsd:sequence> + <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /> + </xsd:sequence> + <xsd:attribute name="name" type="xsd:string" use="required" /> + </xsd:complexType> + </xsd:element> + </xsd:choice> + </xsd:complexType> + </xsd:element> + </xsd:schema> + <resheader name="resmimetype"> + <value>text/microsoft-resx</value> + </resheader> + <resheader name="version"> + <value>2.0</value> + </resheader> + <resheader name="reader"> + <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> + </resheader> + <resheader name="writer"> + <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> + </resheader> + <data name="ArgumentMustBeGreaterThanOrEqualTo" xml:space="preserve"> + <value>The argument '{0}' must be greater than or equal to {1}.</value> + </data> + <data name="CannotCastJsonValue" xml:space="preserve"> + <value>Unable to cast object of type '{0}' to type '{1}'.</value> + </data> + <data name="CannotReadAsType" xml:space="preserve"> + <value>CannotReadAsType=Cannot read '{0}' as '{1}' type.</value> + </data> + <data name="CannotReadPrimitiveAsType" xml:space="preserve"> + <value>Cannot read JsonPrimitive value '{0}' as '{1}'.</value> + </data> + <data name="DynamicPropertyNotDefined" xml:space="preserve"> + <value>'{0}' does not contain a definition for property '{1}'.</value> + </data> + <data name="IncorrectJsonFormat" xml:space="preserve"> + <value>The input source is not correctly formatted.</value> + </data> + <data name="IndexerNotSupportedOnJsonType" xml:space="preserve"> + <value>'{0}' type indexer is not supported on JsonValue of 'JsonType.{1}' type.</value> + </data> + <data name="InvalidCastNonNullable" xml:space="preserve"> + <value>Cannot convert null to '{0}' because it is a non-nullable value type.</value> + </data> + <data name="InvalidDateFormat" xml:space="preserve"> + <value>Cannot cast JsonPrimitive value '{0}' as '{1}'. It is not in a valid date format.</value> + </data> + <data name="InvalidIndexType" xml:space="preserve"> + <value>Invalid '{0}' index type; only 'System.String' and non-negative 'System.Int32' types are supported.</value> + </data> + <data name="InvalidJsonPrimitive" xml:space="preserve"> + <value>Invalid JSON primitive: {0}.</value> + </data> + <data name="InvalidUriFormat" xml:space="preserve"> + <value>Cannot cast '{0}' value '{1}.{2}' as a type of '{3}'. The provided string is not a valid relative or absolute '{3}'.</value> + </data> + <data name="JsonStringCannotBeEmpty" xml:space="preserve"> + <value>An empty string cannot be parsed as JSON.</value> + </data> + <data name="NonSingleNonNullIndexNotSupported" xml:space="preserve"> + <value>Null index or multidimensional indexing is not supported by this indexer; use 'System.Int32' or 'System.String' for array and object indexing respectively.</value> + </data> + <data name="OverflowReadAs" xml:space="preserve"> + <value>Cannot cast JsonPrimitive value '{0}' as '{1}'. The value is either too large or too small for the specified CLR type.</value> + </data> + <data name="TypeNotSupported" xml:space="preserve"> + <value>Object type not supported.</value> + </data> + <data name="UseOfDefaultNotAllowed" xml:space="preserve"> + <value>Operation not supported on JsonValue instances of 'JsonType.Default' type.</value> + </data> +</root>
\ No newline at end of file |