mirror of https://github.com/icsharpcode/ILSpy.git
12 changed files with 2454 additions and 11 deletions
@ -0,0 +1,181 @@ |
|||||||
|
// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
|
||||||
|
|
||||||
|
namespace LightJson |
||||||
|
{ |
||||||
|
using System; |
||||||
|
using System.Collections.Generic; |
||||||
|
using System.Diagnostics; |
||||||
|
using System.Diagnostics.CodeAnalysis; |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents an ordered collection of JsonValues.
|
||||||
|
/// </summary>
|
||||||
|
[DebuggerDisplay("Count = {Count}")] |
||||||
|
[DebuggerTypeProxy(typeof(JsonArrayDebugView))] |
||||||
|
internal sealed class JsonArray : IEnumerable<JsonValue> |
||||||
|
{ |
||||||
|
private IList<JsonValue> items; |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="JsonArray"/> class.
|
||||||
|
/// </summary>
|
||||||
|
public JsonArray() |
||||||
|
{ |
||||||
|
this.items = new List<JsonValue>(); |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="JsonArray"/> class, adding the given values to the collection.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="values">The values to be added to this collection.</param>
|
||||||
|
public JsonArray(params JsonValue[] values) |
||||||
|
: this() |
||||||
|
{ |
||||||
|
if (values == null) { |
||||||
|
throw new ArgumentNullException(nameof(values)); |
||||||
|
} |
||||||
|
|
||||||
|
foreach (var value in values) { |
||||||
|
this.items.Add(value); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the number of values in this collection.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The number of values in this collection.</value>
|
||||||
|
public int Count { |
||||||
|
get { |
||||||
|
return this.items.Count; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the value at the given index.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="index">The zero-based index of the value to get or set.</param>
|
||||||
|
/// <remarks>
|
||||||
|
/// The getter will return JsonValue.Null if the given index is out of range.
|
||||||
|
/// </remarks>
|
||||||
|
public JsonValue this[int index] { |
||||||
|
get { |
||||||
|
if (index >= 0 && index < this.items.Count) { |
||||||
|
return this.items[index]; |
||||||
|
} else { |
||||||
|
return JsonValue.Null; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
set { |
||||||
|
this.items[index] = value; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds the given value to this collection.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value">The value to be added.</param>
|
||||||
|
/// <returns>Returns this collection.</returns>
|
||||||
|
public JsonArray Add(JsonValue value) |
||||||
|
{ |
||||||
|
this.items.Add(value); |
||||||
|
return this; |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Inserts the given value at the given index in this collection.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="index">The index where the given value will be inserted.</param>
|
||||||
|
/// <param name="value">The value to be inserted into this collection.</param>
|
||||||
|
/// <returns>Returns this collection.</returns>
|
||||||
|
public JsonArray Insert(int index, JsonValue value) |
||||||
|
{ |
||||||
|
this.items.Insert(index, value); |
||||||
|
return this; |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Removes the value at the given index.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="index">The index of the value to be removed.</param>
|
||||||
|
/// <returns>Return this collection.</returns>
|
||||||
|
public JsonArray Remove(int index) |
||||||
|
{ |
||||||
|
this.items.RemoveAt(index); |
||||||
|
return this; |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Clears the contents of this collection.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>Returns this collection.</returns>
|
||||||
|
public JsonArray Clear() |
||||||
|
{ |
||||||
|
this.items.Clear(); |
||||||
|
return this; |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Determines whether the given item is in the JsonArray.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="item">The item to locate in the JsonArray.</param>
|
||||||
|
/// <returns>Returns true if the item is found; otherwise, false.</returns>
|
||||||
|
public bool Contains(JsonValue item) |
||||||
|
{ |
||||||
|
return this.items.Contains(item); |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Determines the index of the given item in this JsonArray.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="item">The item to locate in this JsonArray.</param>
|
||||||
|
/// <returns>The index of the item, if found. Otherwise, returns -1.</returns>
|
||||||
|
public int IndexOf(JsonValue item) |
||||||
|
{ |
||||||
|
return this.items.IndexOf(item); |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns an enumerator that iterates through the collection.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>The enumerator that iterates through the collection.</returns>
|
||||||
|
public IEnumerator<JsonValue> GetEnumerator() |
||||||
|
{ |
||||||
|
return this.items.GetEnumerator(); |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns an enumerator that iterates through the collection.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>The enumerator that iterates through the collection.</returns>
|
||||||
|
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() |
||||||
|
{ |
||||||
|
return this.GetEnumerator(); |
||||||
|
} |
||||||
|
|
||||||
|
[ExcludeFromCodeCoverage] |
||||||
|
private class JsonArrayDebugView |
||||||
|
{ |
||||||
|
private JsonArray jsonArray; |
||||||
|
|
||||||
|
public JsonArrayDebugView(JsonArray jsonArray) |
||||||
|
{ |
||||||
|
this.jsonArray = jsonArray; |
||||||
|
} |
||||||
|
|
||||||
|
[DebuggerBrowsable(DebuggerBrowsableState.RootHidden)] |
||||||
|
public JsonValue[] Items { |
||||||
|
get { |
||||||
|
var items = new JsonValue[this.jsonArray.Count]; |
||||||
|
|
||||||
|
for (int i = 0; i < this.jsonArray.Count; i += 1) { |
||||||
|
items[i] = this.jsonArray[i]; |
||||||
|
} |
||||||
|
|
||||||
|
return items; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,241 @@ |
|||||||
|
// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
|
||||||
|
|
||||||
|
namespace LightJson |
||||||
|
{ |
||||||
|
using System.Collections.Generic; |
||||||
|
using System.Diagnostics; |
||||||
|
using System.Diagnostics.CodeAnalysis; |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a key-value pair collection of JsonValue objects.
|
||||||
|
/// </summary>
|
||||||
|
[DebuggerDisplay("Count = {Count}")] |
||||||
|
[DebuggerTypeProxy(typeof(JsonObjectDebugView))] |
||||||
|
internal sealed class JsonObject : IEnumerable<KeyValuePair<string, JsonValue>>, IEnumerable<JsonValue> |
||||||
|
{ |
||||||
|
private IDictionary<string, JsonValue> properties; |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="JsonObject"/> class.
|
||||||
|
/// </summary>
|
||||||
|
public JsonObject() |
||||||
|
{ |
||||||
|
this.properties = new Dictionary<string, JsonValue>(); |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the number of properties in this JsonObject.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The number of properties in this JsonObject.</value>
|
||||||
|
public int Count { |
||||||
|
get { |
||||||
|
return this.properties.Count; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the property with the given key.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="key">The key of the property to get or set.</param>
|
||||||
|
/// <remarks>
|
||||||
|
/// The getter will return JsonValue.Null if the given key is not assosiated with any value.
|
||||||
|
/// </remarks>
|
||||||
|
public JsonValue this[string key] { |
||||||
|
get { |
||||||
|
JsonValue value; |
||||||
|
|
||||||
|
if (this.properties.TryGetValue(key, out value)) { |
||||||
|
return value; |
||||||
|
} else { |
||||||
|
return JsonValue.Null; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
set { |
||||||
|
this.properties[key] = value; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds a key with a null value to this collection.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="key">The key of the property to be added.</param>
|
||||||
|
/// <remarks>Returns this JsonObject.</remarks>
|
||||||
|
/// <returns>The <see cref="JsonObject"/> that was added.</returns>
|
||||||
|
public JsonObject Add(string key) |
||||||
|
{ |
||||||
|
return this.Add(key, JsonValue.Null); |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds a value associated with a key to this collection.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="key">The key of the property to be added.</param>
|
||||||
|
/// <param name="value">The value of the property to be added.</param>
|
||||||
|
/// <returns>Returns this JsonObject.</returns>
|
||||||
|
public JsonObject Add(string key, JsonValue value) |
||||||
|
{ |
||||||
|
this.properties.Add(key, value); |
||||||
|
return this; |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Removes a property with the given key.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="key">The key of the property to be removed.</param>
|
||||||
|
/// <returns>
|
||||||
|
/// Returns true if the given key is found and removed; otherwise, false.
|
||||||
|
/// </returns>
|
||||||
|
public bool Remove(string key) |
||||||
|
{ |
||||||
|
return this.properties.Remove(key); |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Clears the contents of this collection.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>Returns this JsonObject.</returns>
|
||||||
|
public JsonObject Clear() |
||||||
|
{ |
||||||
|
this.properties.Clear(); |
||||||
|
return this; |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Changes the key of one of the items in the collection.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// This method has no effects if the <i>oldKey</i> does not exists.
|
||||||
|
/// If the <i>newKey</i> already exists, the value will be overwritten.
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="oldKey">The name of the key to be changed.</param>
|
||||||
|
/// <param name="newKey">The new name of the key.</param>
|
||||||
|
/// <returns>Returns this JsonObject.</returns>
|
||||||
|
public JsonObject Rename(string oldKey, string newKey) |
||||||
|
{ |
||||||
|
if (oldKey == newKey) { |
||||||
|
// Renaming to the same name just does nothing
|
||||||
|
return this; |
||||||
|
} |
||||||
|
|
||||||
|
JsonValue value; |
||||||
|
|
||||||
|
if (this.properties.TryGetValue(oldKey, out value)) { |
||||||
|
this[newKey] = value; |
||||||
|
this.Remove(oldKey); |
||||||
|
} |
||||||
|
|
||||||
|
return this; |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Determines whether this collection contains an item assosiated with the given key.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="key">The key to locate in this collection.</param>
|
||||||
|
/// <returns>Returns true if the key is found; otherwise, false.</returns>
|
||||||
|
public bool ContainsKey(string key) |
||||||
|
{ |
||||||
|
return this.properties.ContainsKey(key); |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Determines whether this collection contains the given JsonValue.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value">The value to locate in this collection.</param>
|
||||||
|
/// <returns>Returns true if the value is found; otherwise, false.</returns>
|
||||||
|
public bool Contains(JsonValue value) |
||||||
|
{ |
||||||
|
return this.properties.Values.Contains(value); |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns an enumerator that iterates through this collection.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>The enumerator that iterates through this collection.</returns>
|
||||||
|
public IEnumerator<KeyValuePair<string, JsonValue>> GetEnumerator() |
||||||
|
{ |
||||||
|
return this.properties.GetEnumerator(); |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns an enumerator that iterates through this collection.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>The enumerator that iterates through this collection.</returns>
|
||||||
|
IEnumerator<JsonValue> IEnumerable<JsonValue>.GetEnumerator() |
||||||
|
{ |
||||||
|
return this.properties.Values.GetEnumerator(); |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns an enumerator that iterates through this collection.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>The enumerator that iterates through this collection.</returns>
|
||||||
|
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() |
||||||
|
{ |
||||||
|
return this.GetEnumerator(); |
||||||
|
} |
||||||
|
|
||||||
|
[ExcludeFromCodeCoverage] |
||||||
|
private class JsonObjectDebugView |
||||||
|
{ |
||||||
|
private JsonObject jsonObject; |
||||||
|
|
||||||
|
public JsonObjectDebugView(JsonObject jsonObject) |
||||||
|
{ |
||||||
|
this.jsonObject = jsonObject; |
||||||
|
} |
||||||
|
|
||||||
|
[DebuggerBrowsable(DebuggerBrowsableState.RootHidden)] |
||||||
|
public KeyValuePair[] Keys { |
||||||
|
get { |
||||||
|
var keys = new KeyValuePair[this.jsonObject.Count]; |
||||||
|
|
||||||
|
var i = 0; |
||||||
|
foreach (var property in this.jsonObject) { |
||||||
|
keys[i] = new KeyValuePair(property.Key, property.Value); |
||||||
|
i += 1; |
||||||
|
} |
||||||
|
|
||||||
|
return keys; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
[DebuggerDisplay("{value.ToString(),nq}", Name = "{key}", Type = "JsonValue({Type})")] |
||||||
|
public class KeyValuePair |
||||||
|
{ |
||||||
|
[DebuggerBrowsable(DebuggerBrowsableState.Never)] |
||||||
|
private string key; |
||||||
|
|
||||||
|
[DebuggerBrowsable(DebuggerBrowsableState.Never)] |
||||||
|
private JsonValue value; |
||||||
|
|
||||||
|
public KeyValuePair(string key, JsonValue value) |
||||||
|
{ |
||||||
|
this.key = key; |
||||||
|
this.value = value; |
||||||
|
} |
||||||
|
|
||||||
|
[DebuggerBrowsable(DebuggerBrowsableState.RootHidden)] |
||||||
|
public object View { |
||||||
|
get { |
||||||
|
if (this.value.IsJsonObject) { |
||||||
|
return (JsonObject)this.value; |
||||||
|
} else if (this.value.IsJsonArray) { |
||||||
|
return (JsonArray)this.value; |
||||||
|
} else { |
||||||
|
return this.value; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
[DebuggerBrowsable(DebuggerBrowsableState.Never)] |
||||||
|
private JsonValueType Type { |
||||||
|
get { |
||||||
|
return this.value.Type; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,769 @@ |
|||||||
|
// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
|
||||||
|
|
||||||
|
namespace LightJson |
||||||
|
{ |
||||||
|
using System; |
||||||
|
using System.Collections.Generic; |
||||||
|
using System.Diagnostics; |
||||||
|
using System.Diagnostics.CodeAnalysis; |
||||||
|
using System.Globalization; |
||||||
|
using LightJson.Serialization; |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A wrapper object that contains a valid JSON value.
|
||||||
|
/// </summary>
|
||||||
|
[DebuggerDisplay("{ToString(),nq}", Type = "JsonValue({Type})")] |
||||||
|
[DebuggerTypeProxy(typeof(JsonValueDebugView))] |
||||||
|
internal struct JsonValue |
||||||
|
{ |
||||||
|
/// <summary>
|
||||||
|
/// Represents a null JsonValue.
|
||||||
|
/// </summary>
|
||||||
|
public static readonly JsonValue Null = new JsonValue(JsonValueType.Null, default(double), null); |
||||||
|
|
||||||
|
private readonly JsonValueType type; |
||||||
|
private readonly object reference; |
||||||
|
private readonly double value; |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="JsonValue"/> struct, representing a Boolean value.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value">The value to be wrapped.</param>
|
||||||
|
public JsonValue(bool? value) |
||||||
|
{ |
||||||
|
if (value.HasValue) { |
||||||
|
this.reference = null; |
||||||
|
|
||||||
|
this.type = JsonValueType.Boolean; |
||||||
|
|
||||||
|
this.value = value.Value ? 1 : 0; |
||||||
|
} else { |
||||||
|
this = JsonValue.Null; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="JsonValue"/> struct, representing a Number value.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value">The value to be wrapped.</param>
|
||||||
|
public JsonValue(double? value) |
||||||
|
{ |
||||||
|
if (value.HasValue) { |
||||||
|
this.reference = null; |
||||||
|
|
||||||
|
this.type = JsonValueType.Number; |
||||||
|
|
||||||
|
this.value = value.Value; |
||||||
|
} else { |
||||||
|
this = JsonValue.Null; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="JsonValue"/> struct, representing a String value.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value">The value to be wrapped.</param>
|
||||||
|
public JsonValue(string value) |
||||||
|
{ |
||||||
|
if (value != null) { |
||||||
|
this.value = default(double); |
||||||
|
|
||||||
|
this.type = JsonValueType.String; |
||||||
|
|
||||||
|
this.reference = value; |
||||||
|
} else { |
||||||
|
this = JsonValue.Null; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="JsonValue"/> struct, representing a JsonObject.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value">The value to be wrapped.</param>
|
||||||
|
public JsonValue(JsonObject value) |
||||||
|
{ |
||||||
|
if (value != null) { |
||||||
|
this.value = default(double); |
||||||
|
|
||||||
|
this.type = JsonValueType.Object; |
||||||
|
|
||||||
|
this.reference = value; |
||||||
|
} else { |
||||||
|
this = JsonValue.Null; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="JsonValue"/> struct, representing a Array reference value.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value">The value to be wrapped.</param>
|
||||||
|
public JsonValue(JsonArray value) |
||||||
|
{ |
||||||
|
if (value != null) { |
||||||
|
this.value = default(double); |
||||||
|
|
||||||
|
this.type = JsonValueType.Array; |
||||||
|
|
||||||
|
this.reference = value; |
||||||
|
} else { |
||||||
|
this = JsonValue.Null; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="JsonValue"/> struct.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="type">The Json type of the JsonValue.</param>
|
||||||
|
/// <param name="value">
|
||||||
|
/// The internal value of the JsonValue.
|
||||||
|
/// This is used when the Json type is Number or Boolean.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="reference">
|
||||||
|
/// The internal value reference of the JsonValue.
|
||||||
|
/// This value is used when the Json type is String, JsonObject, or JsonArray.
|
||||||
|
/// </param>
|
||||||
|
private JsonValue(JsonValueType type, double value, object reference) |
||||||
|
{ |
||||||
|
this.type = type; |
||||||
|
this.value = value; |
||||||
|
this.reference = reference; |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the type of this JsonValue.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The type of this JsonValue.</value>
|
||||||
|
public JsonValueType Type { |
||||||
|
get { |
||||||
|
return this.type; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a value indicating whether this JsonValue is Null.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>A value indicating whether this JsonValue is Null.</value>
|
||||||
|
public bool IsNull { |
||||||
|
get { |
||||||
|
return this.Type == JsonValueType.Null; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a value indicating whether this JsonValue is a Boolean.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>A value indicating whether this JsonValue is a Boolean.</value>
|
||||||
|
public bool IsBoolean { |
||||||
|
get { |
||||||
|
return this.Type == JsonValueType.Boolean; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a value indicating whether this JsonValue is an Integer.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>A value indicating whether this JsonValue is an Integer.</value>
|
||||||
|
public bool IsInteger { |
||||||
|
get { |
||||||
|
if (!this.IsNumber) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
var value = this.value; |
||||||
|
return unchecked((int)value) == value; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a value indicating whether this JsonValue is a Number.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>A value indicating whether this JsonValue is a Number.</value>
|
||||||
|
public bool IsNumber { |
||||||
|
get { |
||||||
|
return this.Type == JsonValueType.Number; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a value indicating whether this JsonValue is a String.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>A value indicating whether this JsonValue is a String.</value>
|
||||||
|
public bool IsString { |
||||||
|
get { |
||||||
|
return this.Type == JsonValueType.String; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a value indicating whether this JsonValue is a JsonObject.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>A value indicating whether this JsonValue is a JsonObject.</value>
|
||||||
|
public bool IsJsonObject { |
||||||
|
get { |
||||||
|
return this.Type == JsonValueType.Object; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a value indicating whether this JsonValue is a JsonArray.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>A value indicating whether this JsonValue is a JsonArray.</value>
|
||||||
|
public bool IsJsonArray { |
||||||
|
get { |
||||||
|
return this.Type == JsonValueType.Array; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a value indicating whether this JsonValue represents a DateTime.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>A value indicating whether this JsonValue represents a DateTime.</value>
|
||||||
|
public bool IsDateTime { |
||||||
|
get { |
||||||
|
return this.AsDateTime != null; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a value indicating whether this value is true or false.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>This value as a Boolean type.</value>
|
||||||
|
public bool AsBoolean { |
||||||
|
get { |
||||||
|
switch (this.Type) { |
||||||
|
case JsonValueType.Boolean: |
||||||
|
return this.value == 1; |
||||||
|
|
||||||
|
case JsonValueType.Number: |
||||||
|
return this.value != 0; |
||||||
|
|
||||||
|
case JsonValueType.String: |
||||||
|
return (string)this.reference != string.Empty; |
||||||
|
|
||||||
|
case JsonValueType.Object: |
||||||
|
case JsonValueType.Array: |
||||||
|
return true; |
||||||
|
|
||||||
|
default: |
||||||
|
return false; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets this value as an Integer type.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>This value as an Integer type.</value>
|
||||||
|
public int AsInteger { |
||||||
|
get { |
||||||
|
var value = this.AsNumber; |
||||||
|
|
||||||
|
// Prevent overflow if the value doesn't fit.
|
||||||
|
if (value >= int.MaxValue) { |
||||||
|
return int.MaxValue; |
||||||
|
} |
||||||
|
|
||||||
|
if (value <= int.MinValue) { |
||||||
|
return int.MinValue; |
||||||
|
} |
||||||
|
|
||||||
|
return (int)value; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets this value as a Number type.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>This value as a Number type.</value>
|
||||||
|
public double AsNumber { |
||||||
|
get { |
||||||
|
switch (this.Type) { |
||||||
|
case JsonValueType.Boolean: |
||||||
|
return (this.value == 1) |
||||||
|
? 1 |
||||||
|
: 0; |
||||||
|
|
||||||
|
case JsonValueType.Number: |
||||||
|
return this.value; |
||||||
|
|
||||||
|
case JsonValueType.String: |
||||||
|
double number; |
||||||
|
if (double.TryParse((string)this.reference, NumberStyles.Float, CultureInfo.InvariantCulture, out number)) { |
||||||
|
return number; |
||||||
|
} else { |
||||||
|
goto default; |
||||||
|
} |
||||||
|
|
||||||
|
default: |
||||||
|
return 0; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets this value as a String type.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>This value as a String type.</value>
|
||||||
|
public string AsString { |
||||||
|
get { |
||||||
|
switch (this.Type) { |
||||||
|
case JsonValueType.Boolean: |
||||||
|
return (this.value == 1) |
||||||
|
? "true" |
||||||
|
: "false"; |
||||||
|
|
||||||
|
case JsonValueType.Number: |
||||||
|
return this.value.ToString(CultureInfo.InvariantCulture); |
||||||
|
|
||||||
|
case JsonValueType.String: |
||||||
|
return (string)this.reference; |
||||||
|
|
||||||
|
default: |
||||||
|
return null; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets this value as an JsonObject.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>This value as an JsonObject.</value>
|
||||||
|
public JsonObject AsJsonObject { |
||||||
|
get { |
||||||
|
return this.IsJsonObject |
||||||
|
? (JsonObject)this.reference |
||||||
|
: null; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets this value as an JsonArray.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>This value as an JsonArray.</value>
|
||||||
|
public JsonArray AsJsonArray { |
||||||
|
get { |
||||||
|
return this.IsJsonArray |
||||||
|
? (JsonArray)this.reference |
||||||
|
: null; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets this value as a system.DateTime.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>This value as a system.DateTime.</value>
|
||||||
|
public DateTime? AsDateTime { |
||||||
|
get { |
||||||
|
DateTime value; |
||||||
|
|
||||||
|
if (this.IsString && DateTime.TryParse((string)this.reference, out value)) { |
||||||
|
return value; |
||||||
|
} else { |
||||||
|
return null; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets this (inner) value as a System.object.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>This (inner) value as a System.object.</value>
|
||||||
|
public object AsObject { |
||||||
|
get { |
||||||
|
switch (this.Type) { |
||||||
|
case JsonValueType.Boolean: |
||||||
|
case JsonValueType.Number: |
||||||
|
return this.value; |
||||||
|
|
||||||
|
case JsonValueType.String: |
||||||
|
case JsonValueType.Object: |
||||||
|
case JsonValueType.Array: |
||||||
|
return this.reference; |
||||||
|
|
||||||
|
default: |
||||||
|
return null; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// <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>
|
||||||
|
/// <exception cref="System.InvalidOperationException">
|
||||||
|
/// Thrown when this JsonValue is not a JsonObject.
|
||||||
|
/// </exception>
|
||||||
|
public JsonValue this[string key] { |
||||||
|
get { |
||||||
|
if (this.IsJsonObject) { |
||||||
|
return ((JsonObject)this.reference)[key]; |
||||||
|
} else { |
||||||
|
throw new InvalidOperationException("This value does not represent a JsonObject."); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
set { |
||||||
|
if (this.IsJsonObject) { |
||||||
|
((JsonObject)this.reference)[key] = value; |
||||||
|
} else { |
||||||
|
throw new InvalidOperationException("This value does not represent a JsonObject."); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the value at the specified index.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="index">The zero-based index of the value to get or set.</param>
|
||||||
|
/// <exception cref="System.InvalidOperationException">
|
||||||
|
/// Thrown when this JsonValue is not a JsonArray
|
||||||
|
/// </exception>
|
||||||
|
public JsonValue this[int index] { |
||||||
|
get { |
||||||
|
if (this.IsJsonArray) { |
||||||
|
return ((JsonArray)this.reference)[index]; |
||||||
|
} else { |
||||||
|
throw new InvalidOperationException("This value does not represent a JsonArray."); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
set { |
||||||
|
if (this.IsJsonArray) { |
||||||
|
((JsonArray)this.reference)[index] = value; |
||||||
|
} else { |
||||||
|
throw new InvalidOperationException("This value does not represent a JsonArray."); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Converts the given nullable boolean into a JsonValue.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value">The value to be converted.</param>
|
||||||
|
public static implicit operator JsonValue(bool? value) |
||||||
|
{ |
||||||
|
return new JsonValue(value); |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Converts the given nullable double into a JsonValue.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value">The value to be converted.</param>
|
||||||
|
public static implicit operator JsonValue(double? value) |
||||||
|
{ |
||||||
|
return new JsonValue(value); |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Converts the given string into a JsonValue.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value">The value to be converted.</param>
|
||||||
|
public static implicit operator JsonValue(string value) |
||||||
|
{ |
||||||
|
return new JsonValue(value); |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Converts the given JsonObject into a JsonValue.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value">The value to be converted.</param>
|
||||||
|
public static implicit operator JsonValue(JsonObject value) |
||||||
|
{ |
||||||
|
return new JsonValue(value); |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Converts the given JsonArray into a JsonValue.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value">The value to be converted.</param>
|
||||||
|
public static implicit operator JsonValue(JsonArray value) |
||||||
|
{ |
||||||
|
return new JsonValue(value); |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Converts the given DateTime? into a JsonValue.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// The DateTime value will be stored as a string using ISO 8601 format,
|
||||||
|
/// since JSON does not define a DateTime type.
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="value">The value to be converted.</param>
|
||||||
|
public static implicit operator JsonValue(DateTime? value) |
||||||
|
{ |
||||||
|
if (value == null) { |
||||||
|
return JsonValue.Null; |
||||||
|
} |
||||||
|
|
||||||
|
return new JsonValue(value.Value.ToString("o")); |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Converts the given JsonValue into an Int.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="jsonValue">The JsonValue to be converted.</param>
|
||||||
|
public static explicit operator int(JsonValue jsonValue) |
||||||
|
{ |
||||||
|
if (jsonValue.IsInteger) { |
||||||
|
return jsonValue.AsInteger; |
||||||
|
} else { |
||||||
|
return 0; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Converts the given JsonValue into a nullable Int.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="jsonValue">The JsonValue to be converted.</param>
|
||||||
|
/// <exception cref="System.InvalidCastException">
|
||||||
|
/// Throws System.InvalidCastException when the inner value type of the
|
||||||
|
/// JsonValue is not the desired type of the conversion.
|
||||||
|
/// </exception>
|
||||||
|
public static explicit operator int? (JsonValue jsonValue) |
||||||
|
{ |
||||||
|
if (jsonValue.IsNull) { |
||||||
|
return null; |
||||||
|
} else { |
||||||
|
return (int)jsonValue; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Converts the given JsonValue into a Bool.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="jsonValue">The JsonValue to be converted.</param>
|
||||||
|
public static explicit operator bool(JsonValue jsonValue) |
||||||
|
{ |
||||||
|
if (jsonValue.IsBoolean) { |
||||||
|
return jsonValue.value == 1; |
||||||
|
} else { |
||||||
|
return false; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Converts the given JsonValue into a nullable Bool.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="jsonValue">The JsonValue to be converted.</param>
|
||||||
|
/// <exception cref="System.InvalidCastException">
|
||||||
|
/// Throws System.InvalidCastException when the inner value type of the
|
||||||
|
/// JsonValue is not the desired type of the conversion.
|
||||||
|
/// </exception>
|
||||||
|
public static explicit operator bool? (JsonValue jsonValue) |
||||||
|
{ |
||||||
|
if (jsonValue.IsNull) { |
||||||
|
return null; |
||||||
|
} else { |
||||||
|
return (bool)jsonValue; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Converts the given JsonValue into a Double.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="jsonValue">The JsonValue to be converted.</param>
|
||||||
|
public static explicit operator double(JsonValue jsonValue) |
||||||
|
{ |
||||||
|
if (jsonValue.IsNumber) { |
||||||
|
return jsonValue.value; |
||||||
|
} else { |
||||||
|
return double.NaN; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Converts the given JsonValue into a nullable Double.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="jsonValue">The JsonValue to be converted.</param>
|
||||||
|
/// <exception cref="System.InvalidCastException">
|
||||||
|
/// Throws System.InvalidCastException when the inner value type of the
|
||||||
|
/// JsonValue is not the desired type of the conversion.
|
||||||
|
/// </exception>
|
||||||
|
public static explicit operator double? (JsonValue jsonValue) |
||||||
|
{ |
||||||
|
if (jsonValue.IsNull) { |
||||||
|
return null; |
||||||
|
} else { |
||||||
|
return (double)jsonValue; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Converts the given JsonValue into a String.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="jsonValue">The JsonValue to be converted.</param>
|
||||||
|
public static explicit operator string(JsonValue jsonValue) |
||||||
|
{ |
||||||
|
if (jsonValue.IsString || jsonValue.IsNull) { |
||||||
|
return jsonValue.reference as string; |
||||||
|
} else { |
||||||
|
return null; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Converts the given JsonValue into a JsonObject.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="jsonValue">The JsonValue to be converted.</param>
|
||||||
|
public static explicit operator JsonObject(JsonValue jsonValue) |
||||||
|
{ |
||||||
|
if (jsonValue.IsJsonObject || jsonValue.IsNull) { |
||||||
|
return jsonValue.reference as JsonObject; |
||||||
|
} else { |
||||||
|
return null; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Converts the given JsonValue into a JsonArray.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="jsonValue">The JsonValue to be converted.</param>
|
||||||
|
public static explicit operator JsonArray(JsonValue jsonValue) |
||||||
|
{ |
||||||
|
if (jsonValue.IsJsonArray || jsonValue.IsNull) { |
||||||
|
return jsonValue.reference as JsonArray; |
||||||
|
} else { |
||||||
|
return null; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Converts the given JsonValue into a DateTime.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="jsonValue">The JsonValue to be converted.</param>
|
||||||
|
public static explicit operator DateTime(JsonValue jsonValue) |
||||||
|
{ |
||||||
|
var dateTime = jsonValue.AsDateTime; |
||||||
|
|
||||||
|
if (dateTime.HasValue) { |
||||||
|
return dateTime.Value; |
||||||
|
} else { |
||||||
|
return DateTime.MinValue; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Converts the given JsonValue into a nullable DateTime.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="jsonValue">The JsonValue to be converted.</param>
|
||||||
|
public static explicit operator DateTime? (JsonValue jsonValue) |
||||||
|
{ |
||||||
|
if (jsonValue.IsDateTime || jsonValue.IsNull) { |
||||||
|
return jsonValue.AsDateTime; |
||||||
|
} else { |
||||||
|
return null; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns a value indicating whether the two given JsonValues are equal.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="a">First JsonValue to compare.</param>
|
||||||
|
/// <param name="b">Second JsonValue to compare.</param>
|
||||||
|
public static bool operator ==(JsonValue a, JsonValue b) |
||||||
|
{ |
||||||
|
return (a.Type == b.Type) |
||||||
|
&& (a.value == b.value) |
||||||
|
&& Equals(a.reference, b.reference); |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns a value indicating whether the two given JsonValues are unequal.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="a">First JsonValue to compare.</param>
|
||||||
|
/// <param name="b">Second JsonValue to compare.</param>
|
||||||
|
public static bool operator !=(JsonValue a, JsonValue b) |
||||||
|
{ |
||||||
|
return !(a == b); |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns a JsonValue by parsing the given string.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="text">The JSON-formatted string to be parsed.</param>
|
||||||
|
/// <returns>The <see cref="JsonValue"/> representing the parsed text.</returns>
|
||||||
|
public static JsonValue Parse(string text) |
||||||
|
{ |
||||||
|
return JsonReader.Parse(text); |
||||||
|
} |
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override bool Equals(object obj) |
||||||
|
{ |
||||||
|
if (obj == null) { |
||||||
|
return this.IsNull; |
||||||
|
} |
||||||
|
|
||||||
|
var jsonValue = obj as JsonValue?; |
||||||
|
if (jsonValue == null) { |
||||||
|
return false; |
||||||
|
} else { |
||||||
|
return this == jsonValue.Value; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override int GetHashCode() |
||||||
|
{ |
||||||
|
if (this.IsNull) { |
||||||
|
return this.Type.GetHashCode(); |
||||||
|
} else { |
||||||
|
return this.Type.GetHashCode() |
||||||
|
^ this.value.GetHashCode() |
||||||
|
^ EqualityComparer<object>.Default.GetHashCode(this.reference); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
[ExcludeFromCodeCoverage] |
||||||
|
private class JsonValueDebugView |
||||||
|
{ |
||||||
|
private JsonValue jsonValue; |
||||||
|
|
||||||
|
public JsonValueDebugView(JsonValue jsonValue) |
||||||
|
{ |
||||||
|
this.jsonValue = jsonValue; |
||||||
|
} |
||||||
|
|
||||||
|
[DebuggerBrowsable(DebuggerBrowsableState.RootHidden)] |
||||||
|
public JsonObject ObjectView { |
||||||
|
get { |
||||||
|
if (this.jsonValue.IsJsonObject) { |
||||||
|
return (JsonObject)this.jsonValue.reference; |
||||||
|
} else { |
||||||
|
return null; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
[DebuggerBrowsable(DebuggerBrowsableState.RootHidden)] |
||||||
|
public JsonArray ArrayView { |
||||||
|
get { |
||||||
|
if (this.jsonValue.IsJsonArray) { |
||||||
|
return (JsonArray)this.jsonValue.reference; |
||||||
|
} else { |
||||||
|
return null; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public JsonValueType Type { |
||||||
|
get { |
||||||
|
return this.jsonValue.Type; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public object Value { |
||||||
|
get { |
||||||
|
if (this.jsonValue.IsJsonObject) { |
||||||
|
return (JsonObject)this.jsonValue.reference; |
||||||
|
} else if (this.jsonValue.IsJsonArray) { |
||||||
|
return (JsonArray)this.jsonValue.reference; |
||||||
|
} else { |
||||||
|
return this.jsonValue; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,41 @@ |
|||||||
|
// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
|
||||||
|
|
||||||
|
namespace LightJson |
||||||
|
{ |
||||||
|
/// <summary>
|
||||||
|
/// Enumerates the types of Json values.
|
||||||
|
/// </summary>
|
||||||
|
internal enum JsonValueType : byte |
||||||
|
{ |
||||||
|
/// <summary>
|
||||||
|
/// A null value.
|
||||||
|
/// </summary>
|
||||||
|
Null = 0, |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A boolean value.
|
||||||
|
/// </summary>
|
||||||
|
Boolean, |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A number value.
|
||||||
|
/// </summary>
|
||||||
|
Number, |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A string value.
|
||||||
|
/// </summary>
|
||||||
|
String, |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// An object value.
|
||||||
|
/// </summary>
|
||||||
|
Object, |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// An array value.
|
||||||
|
/// </summary>
|
||||||
|
Array, |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,102 @@ |
|||||||
|
// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
|
||||||
|
|
||||||
|
namespace LightJson.Serialization |
||||||
|
{ |
||||||
|
using System; |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The exception that is thrown when a JSON message cannot be parsed.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// This exception is only intended to be thrown by LightJson.
|
||||||
|
/// </remarks>
|
||||||
|
internal sealed class JsonParseException : Exception |
||||||
|
{ |
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="JsonParseException"/> class.
|
||||||
|
/// </summary>
|
||||||
|
public JsonParseException() |
||||||
|
: base(GetDefaultMessage(ErrorType.Unknown)) |
||||||
|
{ |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="JsonParseException"/> class with the given error type and position.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="type">The error type that describes the cause of the error.</param>
|
||||||
|
/// <param name="position">The position in the text where the error occurred.</param>
|
||||||
|
public JsonParseException(ErrorType type, TextPosition position) |
||||||
|
: this(GetDefaultMessage(type), type, position) |
||||||
|
{ |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="JsonParseException"/> class with the given message, error type, and position.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="message">The message that describes the error.</param>
|
||||||
|
/// <param name="type">The error type that describes the cause of the error.</param>
|
||||||
|
/// <param name="position">The position in the text where the error occurred.</param>
|
||||||
|
public JsonParseException(string message, ErrorType type, TextPosition position) |
||||||
|
: base(message) |
||||||
|
{ |
||||||
|
this.Type = type; |
||||||
|
this.Position = position; |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Enumerates the types of errors that can occur when parsing a JSON message.
|
||||||
|
/// </summary>
|
||||||
|
public enum ErrorType : int |
||||||
|
{ |
||||||
|
/// <summary>
|
||||||
|
/// Indicates that the cause of the error is unknown.
|
||||||
|
/// </summary>
|
||||||
|
Unknown = 0, |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Indicates that the text ended before the message could be parsed.
|
||||||
|
/// </summary>
|
||||||
|
IncompleteMessage, |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Indicates that a JsonObject contains more than one key with the same name.
|
||||||
|
/// </summary>
|
||||||
|
DuplicateObjectKeys, |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Indicates that the parser encountered and invalid or unexpected character.
|
||||||
|
/// </summary>
|
||||||
|
InvalidOrUnexpectedCharacter, |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the text position where the error occurred.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The text position where the error occurred.</value>
|
||||||
|
public TextPosition Position { get; private set; } |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the type of error that caused the exception to be thrown.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The type of error that caused the exception to be thrown.</value>
|
||||||
|
public ErrorType Type { get; private set; } |
||||||
|
|
||||||
|
private static string GetDefaultMessage(ErrorType type) |
||||||
|
{ |
||||||
|
switch (type) { |
||||||
|
case ErrorType.IncompleteMessage: |
||||||
|
return "The string ended before a value could be parsed."; |
||||||
|
|
||||||
|
case ErrorType.InvalidOrUnexpectedCharacter: |
||||||
|
return "The parser encountered an invalid or unexpected character."; |
||||||
|
|
||||||
|
case ErrorType.DuplicateObjectKeys: |
||||||
|
return "The parser encountered a JsonObject with duplicate keys."; |
||||||
|
|
||||||
|
default: |
||||||
|
return "An error occurred while parsing the JSON message."; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,413 @@ |
|||||||
|
// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
|
||||||
|
|
||||||
|
namespace LightJson.Serialization |
||||||
|
{ |
||||||
|
using System; |
||||||
|
using System.Globalization; |
||||||
|
using System.IO; |
||||||
|
using System.Text; |
||||||
|
using ErrorType = JsonParseException.ErrorType; |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a reader that can read JsonValues.
|
||||||
|
/// </summary>
|
||||||
|
internal sealed class JsonReader |
||||||
|
{ |
||||||
|
private TextScanner scanner; |
||||||
|
|
||||||
|
private JsonReader(TextReader reader) |
||||||
|
{ |
||||||
|
this.scanner = new TextScanner(reader); |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a JsonValue by using the given TextReader.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="reader">The TextReader used to read a JSON message.</param>
|
||||||
|
/// <returns>The parsed <see cref="JsonValue"/>.</returns>
|
||||||
|
public static JsonValue Parse(TextReader reader) |
||||||
|
{ |
||||||
|
if (reader == null) { |
||||||
|
throw new ArgumentNullException(nameof(reader)); |
||||||
|
} |
||||||
|
|
||||||
|
return new JsonReader(reader).Parse(); |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a JsonValue by reader the JSON message in the given string.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="source">The string containing the JSON message.</param>
|
||||||
|
/// <returns>The parsed <see cref="JsonValue"/>.</returns>
|
||||||
|
public static JsonValue Parse(string source) |
||||||
|
{ |
||||||
|
if (source == null) { |
||||||
|
throw new ArgumentNullException(nameof(source)); |
||||||
|
} |
||||||
|
|
||||||
|
using (var reader = new StringReader(source)) { |
||||||
|
return Parse(reader); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private string ReadJsonKey() |
||||||
|
{ |
||||||
|
return this.ReadString(); |
||||||
|
} |
||||||
|
|
||||||
|
private JsonValue ReadJsonValue() |
||||||
|
{ |
||||||
|
this.scanner.SkipWhitespace(); |
||||||
|
|
||||||
|
var next = this.scanner.Peek(); |
||||||
|
|
||||||
|
if (char.IsNumber(next)) { |
||||||
|
return this.ReadNumber(); |
||||||
|
} |
||||||
|
|
||||||
|
switch (next) { |
||||||
|
case '{': |
||||||
|
return this.ReadObject(); |
||||||
|
|
||||||
|
case '[': |
||||||
|
return this.ReadArray(); |
||||||
|
|
||||||
|
case '"': |
||||||
|
return this.ReadString(); |
||||||
|
|
||||||
|
case '-': |
||||||
|
return this.ReadNumber(); |
||||||
|
|
||||||
|
case 't': |
||||||
|
case 'f': |
||||||
|
return this.ReadBoolean(); |
||||||
|
|
||||||
|
case 'n': |
||||||
|
return this.ReadNull(); |
||||||
|
|
||||||
|
default: |
||||||
|
throw new JsonParseException( |
||||||
|
ErrorType.InvalidOrUnexpectedCharacter, |
||||||
|
this.scanner.Position); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private JsonValue ReadNull() |
||||||
|
{ |
||||||
|
this.scanner.Assert("null"); |
||||||
|
return JsonValue.Null; |
||||||
|
} |
||||||
|
|
||||||
|
private JsonValue ReadBoolean() |
||||||
|
{ |
||||||
|
switch (this.scanner.Peek()) { |
||||||
|
case 't': |
||||||
|
this.scanner.Assert("true"); |
||||||
|
return true; |
||||||
|
|
||||||
|
default: |
||||||
|
this.scanner.Assert("false"); |
||||||
|
return false; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private void ReadDigits(StringBuilder builder) |
||||||
|
{ |
||||||
|
while (true) { |
||||||
|
int next = this.scanner.Peek(throwAtEndOfFile: false); |
||||||
|
if (next == -1 || !char.IsNumber((char)next)) { |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
builder.Append(this.scanner.Read()); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private JsonValue ReadNumber() |
||||||
|
{ |
||||||
|
var builder = new StringBuilder(); |
||||||
|
|
||||||
|
if (this.scanner.Peek() == '-') { |
||||||
|
builder.Append(this.scanner.Read()); |
||||||
|
} |
||||||
|
|
||||||
|
if (this.scanner.Peek() == '0') { |
||||||
|
builder.Append(this.scanner.Read()); |
||||||
|
} else { |
||||||
|
this.ReadDigits(builder); |
||||||
|
} |
||||||
|
|
||||||
|
if (this.scanner.Peek(throwAtEndOfFile: false) == '.') { |
||||||
|
builder.Append(this.scanner.Read()); |
||||||
|
this.ReadDigits(builder); |
||||||
|
} |
||||||
|
|
||||||
|
if (this.scanner.Peek(throwAtEndOfFile: false) == 'e' || this.scanner.Peek(throwAtEndOfFile: false) == 'E') { |
||||||
|
builder.Append(this.scanner.Read()); |
||||||
|
|
||||||
|
var next = this.scanner.Peek(); |
||||||
|
|
||||||
|
switch (next) { |
||||||
|
case '+': |
||||||
|
case '-': |
||||||
|
builder.Append(this.scanner.Read()); |
||||||
|
break; |
||||||
|
} |
||||||
|
|
||||||
|
this.ReadDigits(builder); |
||||||
|
} |
||||||
|
|
||||||
|
return double.Parse( |
||||||
|
builder.ToString(), |
||||||
|
CultureInfo.InvariantCulture); |
||||||
|
} |
||||||
|
|
||||||
|
private string ReadString() |
||||||
|
{ |
||||||
|
var builder = new StringBuilder(); |
||||||
|
|
||||||
|
this.scanner.Assert('"'); |
||||||
|
|
||||||
|
while (true) { |
||||||
|
var errorPosition = this.scanner.Position; |
||||||
|
var c = this.scanner.Read(); |
||||||
|
|
||||||
|
if (c == '\\') { |
||||||
|
errorPosition = this.scanner.Position; |
||||||
|
c = this.scanner.Read(); |
||||||
|
|
||||||
|
switch (char.ToLower(c)) { |
||||||
|
case '"': |
||||||
|
case '\\': |
||||||
|
case '/': |
||||||
|
builder.Append(c); |
||||||
|
break; |
||||||
|
case 'b': |
||||||
|
builder.Append('\b'); |
||||||
|
break; |
||||||
|
case 'f': |
||||||
|
builder.Append('\f'); |
||||||
|
break; |
||||||
|
case 'n': |
||||||
|
builder.Append('\n'); |
||||||
|
break; |
||||||
|
case 'r': |
||||||
|
builder.Append('\r'); |
||||||
|
break; |
||||||
|
case 't': |
||||||
|
builder.Append('\t'); |
||||||
|
break; |
||||||
|
case 'u': |
||||||
|
builder.Append(this.ReadUnicodeLiteral()); |
||||||
|
break; |
||||||
|
default: |
||||||
|
throw new JsonParseException( |
||||||
|
ErrorType.InvalidOrUnexpectedCharacter, |
||||||
|
errorPosition); |
||||||
|
} |
||||||
|
} else if (c == '"') { |
||||||
|
break; |
||||||
|
} else { |
||||||
|
if (char.IsControl(c)) { |
||||||
|
throw new JsonParseException( |
||||||
|
ErrorType.InvalidOrUnexpectedCharacter, |
||||||
|
errorPosition); |
||||||
|
} else { |
||||||
|
builder.Append(c); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return builder.ToString(); |
||||||
|
} |
||||||
|
|
||||||
|
private int ReadHexDigit() |
||||||
|
{ |
||||||
|
var errorPosition = this.scanner.Position; |
||||||
|
switch (char.ToUpper(this.scanner.Read())) { |
||||||
|
case '0': |
||||||
|
return 0; |
||||||
|
|
||||||
|
case '1': |
||||||
|
return 1; |
||||||
|
|
||||||
|
case '2': |
||||||
|
return 2; |
||||||
|
|
||||||
|
case '3': |
||||||
|
return 3; |
||||||
|
|
||||||
|
case '4': |
||||||
|
return 4; |
||||||
|
|
||||||
|
case '5': |
||||||
|
return 5; |
||||||
|
|
||||||
|
case '6': |
||||||
|
return 6; |
||||||
|
|
||||||
|
case '7': |
||||||
|
return 7; |
||||||
|
|
||||||
|
case '8': |
||||||
|
return 8; |
||||||
|
|
||||||
|
case '9': |
||||||
|
return 9; |
||||||
|
|
||||||
|
case 'A': |
||||||
|
return 10; |
||||||
|
|
||||||
|
case 'B': |
||||||
|
return 11; |
||||||
|
|
||||||
|
case 'C': |
||||||
|
return 12; |
||||||
|
|
||||||
|
case 'D': |
||||||
|
return 13; |
||||||
|
|
||||||
|
case 'E': |
||||||
|
return 14; |
||||||
|
|
||||||
|
case 'F': |
||||||
|
return 15; |
||||||
|
|
||||||
|
default: |
||||||
|
throw new JsonParseException( |
||||||
|
ErrorType.InvalidOrUnexpectedCharacter, |
||||||
|
errorPosition); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private char ReadUnicodeLiteral() |
||||||
|
{ |
||||||
|
int value = 0; |
||||||
|
|
||||||
|
value += this.ReadHexDigit() * 4096; // 16^3
|
||||||
|
value += this.ReadHexDigit() * 256; // 16^2
|
||||||
|
value += this.ReadHexDigit() * 16; // 16^1
|
||||||
|
value += this.ReadHexDigit(); // 16^0
|
||||||
|
|
||||||
|
return (char)value; |
||||||
|
} |
||||||
|
|
||||||
|
private JsonObject ReadObject() |
||||||
|
{ |
||||||
|
return this.ReadObject(new JsonObject()); |
||||||
|
} |
||||||
|
|
||||||
|
private JsonObject ReadObject(JsonObject jsonObject) |
||||||
|
{ |
||||||
|
this.scanner.Assert('{'); |
||||||
|
|
||||||
|
this.scanner.SkipWhitespace(); |
||||||
|
|
||||||
|
if (this.scanner.Peek() == '}') { |
||||||
|
this.scanner.Read(); |
||||||
|
} else { |
||||||
|
while (true) { |
||||||
|
this.scanner.SkipWhitespace(); |
||||||
|
|
||||||
|
var errorPosition = this.scanner.Position; |
||||||
|
var key = this.ReadJsonKey(); |
||||||
|
|
||||||
|
if (jsonObject.ContainsKey(key)) { |
||||||
|
throw new JsonParseException( |
||||||
|
ErrorType.DuplicateObjectKeys, |
||||||
|
errorPosition); |
||||||
|
} |
||||||
|
|
||||||
|
this.scanner.SkipWhitespace(); |
||||||
|
|
||||||
|
this.scanner.Assert(':'); |
||||||
|
|
||||||
|
this.scanner.SkipWhitespace(); |
||||||
|
|
||||||
|
var value = this.ReadJsonValue(); |
||||||
|
|
||||||
|
jsonObject.Add(key, value); |
||||||
|
|
||||||
|
this.scanner.SkipWhitespace(); |
||||||
|
|
||||||
|
errorPosition = this.scanner.Position; |
||||||
|
var next = this.scanner.Read(); |
||||||
|
if (next == ',') { |
||||||
|
// Allow trailing commas in objects
|
||||||
|
this.scanner.SkipWhitespace(); |
||||||
|
if (this.scanner.Peek() == '}') { |
||||||
|
next = this.scanner.Read(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if (next == '}') { |
||||||
|
break; |
||||||
|
} else if (next == ',') { |
||||||
|
continue; |
||||||
|
} else { |
||||||
|
throw new JsonParseException( |
||||||
|
ErrorType.InvalidOrUnexpectedCharacter, |
||||||
|
errorPosition); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return jsonObject; |
||||||
|
} |
||||||
|
|
||||||
|
private JsonArray ReadArray() |
||||||
|
{ |
||||||
|
return this.ReadArray(new JsonArray()); |
||||||
|
} |
||||||
|
|
||||||
|
private JsonArray ReadArray(JsonArray jsonArray) |
||||||
|
{ |
||||||
|
this.scanner.Assert('['); |
||||||
|
|
||||||
|
this.scanner.SkipWhitespace(); |
||||||
|
|
||||||
|
if (this.scanner.Peek() == ']') { |
||||||
|
this.scanner.Read(); |
||||||
|
} else { |
||||||
|
while (true) { |
||||||
|
this.scanner.SkipWhitespace(); |
||||||
|
|
||||||
|
var value = this.ReadJsonValue(); |
||||||
|
|
||||||
|
jsonArray.Add(value); |
||||||
|
|
||||||
|
this.scanner.SkipWhitespace(); |
||||||
|
|
||||||
|
var errorPosition = this.scanner.Position; |
||||||
|
var next = this.scanner.Read(); |
||||||
|
if (next == ',') { |
||||||
|
// Allow trailing commas in arrays
|
||||||
|
this.scanner.SkipWhitespace(); |
||||||
|
if (this.scanner.Peek() == ']') { |
||||||
|
next = this.scanner.Read(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if (next == ']') { |
||||||
|
break; |
||||||
|
} else if (next == ',') { |
||||||
|
continue; |
||||||
|
} else { |
||||||
|
throw new JsonParseException( |
||||||
|
ErrorType.InvalidOrUnexpectedCharacter, |
||||||
|
errorPosition); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return jsonArray; |
||||||
|
} |
||||||
|
|
||||||
|
private JsonValue Parse() |
||||||
|
{ |
||||||
|
this.scanner.SkipWhitespace(); |
||||||
|
return this.ReadJsonValue(); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,97 @@ |
|||||||
|
// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
|
||||||
|
|
||||||
|
namespace LightJson.Serialization |
||||||
|
{ |
||||||
|
using System; |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The exception that is thrown when a JSON value cannot be serialized.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// This exception is only intended to be thrown by LightJson.
|
||||||
|
/// </remarks>
|
||||||
|
internal sealed class JsonSerializationException : Exception |
||||||
|
{ |
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="JsonSerializationException"/> class.
|
||||||
|
/// </summary>
|
||||||
|
public JsonSerializationException() |
||||||
|
: base(GetDefaultMessage(ErrorType.Unknown)) |
||||||
|
{ |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="JsonSerializationException"/> class with the given error type.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="type">The error type that describes the cause of the error.</param>
|
||||||
|
public JsonSerializationException(ErrorType type) |
||||||
|
: this(GetDefaultMessage(type), type) |
||||||
|
{ |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="JsonSerializationException"/> class with the given message and
|
||||||
|
/// error type.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="message">The message that describes the error.</param>
|
||||||
|
/// <param name="type">The error type that describes the cause of the error.</param>
|
||||||
|
public JsonSerializationException(string message, ErrorType type) |
||||||
|
: base(message) |
||||||
|
{ |
||||||
|
this.Type = type; |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Enumerates the types of errors that can occur during serialization.
|
||||||
|
/// </summary>
|
||||||
|
public enum ErrorType |
||||||
|
{ |
||||||
|
/// <summary>
|
||||||
|
/// Indicates that the cause of the error is unknown.
|
||||||
|
/// </summary>
|
||||||
|
Unknown = 0, |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Indicates that the writer encountered an invalid number value (NAN, infinity) during serialization.
|
||||||
|
/// </summary>
|
||||||
|
InvalidNumber, |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Indicates that the object been serialized contains an invalid JSON value type.
|
||||||
|
/// That is, a value type that is not null, boolean, number, string, object, or array.
|
||||||
|
/// </summary>
|
||||||
|
InvalidValueType, |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Indicates that the object been serialized contains a circular reference.
|
||||||
|
/// </summary>
|
||||||
|
CircularReference, |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the type of error that caused the exception to be thrown.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>
|
||||||
|
/// The type of error that caused the exception to be thrown.
|
||||||
|
/// </value>
|
||||||
|
public ErrorType Type { get; } |
||||||
|
|
||||||
|
private static string GetDefaultMessage(ErrorType type) |
||||||
|
{ |
||||||
|
switch (type) { |
||||||
|
case ErrorType.InvalidNumber: |
||||||
|
return "The value been serialized contains an invalid number value (NAN, infinity)."; |
||||||
|
|
||||||
|
case ErrorType.InvalidValueType: |
||||||
|
return "The value been serialized contains (or is) an invalid JSON type."; |
||||||
|
|
||||||
|
case ErrorType.CircularReference: |
||||||
|
return "The value been serialized contains circular references."; |
||||||
|
|
||||||
|
default: |
||||||
|
return "An error occurred during serialization."; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,349 @@ |
|||||||
|
// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
|
||||||
|
|
||||||
|
namespace LightJson.Serialization |
||||||
|
{ |
||||||
|
using System; |
||||||
|
using System.Collections.Generic; |
||||||
|
using System.Diagnostics; |
||||||
|
using System.Globalization; |
||||||
|
using System.IO; |
||||||
|
using ErrorType = JsonSerializationException.ErrorType; |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a writer that can write string representations of JsonValues.
|
||||||
|
/// </summary>
|
||||||
|
internal sealed class JsonWriter : IDisposable |
||||||
|
{ |
||||||
|
private int indent; |
||||||
|
private bool isNewLine; |
||||||
|
private TextWriter writer; |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A set of containing all the collection objects (JsonObject/JsonArray) being rendered.
|
||||||
|
/// It is used to prevent circular references; since collections that contain themselves
|
||||||
|
/// will never finish rendering.
|
||||||
|
/// </summary>
|
||||||
|
private HashSet<IEnumerable<JsonValue>> renderingCollections; |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="JsonWriter"/> class.
|
||||||
|
/// </summary>
|
||||||
|
public JsonWriter() |
||||||
|
: this(false) |
||||||
|
{ |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="JsonWriter"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="pretty">
|
||||||
|
/// A value indicating whether the output of the writer should be human-readable.
|
||||||
|
/// </param>
|
||||||
|
public JsonWriter(bool pretty) |
||||||
|
{ |
||||||
|
if (pretty) { |
||||||
|
this.IndentString = "\t"; |
||||||
|
this.SpacingString = " "; |
||||||
|
this.NewLineString = "\n"; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the string representing a indent in the output.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>
|
||||||
|
/// The string representing a indent in the output.
|
||||||
|
/// </value>
|
||||||
|
public string IndentString { get; set; } |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the string representing a space in the output.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>
|
||||||
|
/// The string representing a space in the output.
|
||||||
|
/// </value>
|
||||||
|
public string SpacingString { get; set; } |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the string representing a new line on the output.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>
|
||||||
|
/// The string representing a new line on the output.
|
||||||
|
/// </value>
|
||||||
|
public string NewLineString { get; set; } |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a value indicating whether JsonObject properties should be written in a deterministic order.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>
|
||||||
|
/// A value indicating whether JsonObject properties should be written in a deterministic order.
|
||||||
|
/// </value>
|
||||||
|
public bool SortObjects { get; set; } |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns a string representation of the given JsonValue.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="jsonValue">The JsonValue to serialize.</param>
|
||||||
|
/// <returns>The serialized value.</returns>
|
||||||
|
public string Serialize(JsonValue jsonValue) |
||||||
|
{ |
||||||
|
this.Initialize(); |
||||||
|
|
||||||
|
this.Render(jsonValue); |
||||||
|
|
||||||
|
return this.writer.ToString(); |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Releases all the resources used by this object.
|
||||||
|
/// </summary>
|
||||||
|
public void Dispose() |
||||||
|
{ |
||||||
|
if (this.writer != null) { |
||||||
|
this.writer.Dispose(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private static bool IsValidNumber(double number) |
||||||
|
{ |
||||||
|
return !(double.IsNaN(number) || double.IsInfinity(number)); |
||||||
|
} |
||||||
|
|
||||||
|
private void Initialize() |
||||||
|
{ |
||||||
|
this.indent = 0; |
||||||
|
this.isNewLine = true; |
||||||
|
this.writer = new StringWriter(); |
||||||
|
this.renderingCollections = new HashSet<IEnumerable<JsonValue>>(); |
||||||
|
} |
||||||
|
|
||||||
|
private void Write(string text) |
||||||
|
{ |
||||||
|
if (this.isNewLine) { |
||||||
|
this.isNewLine = false; |
||||||
|
this.WriteIndentation(); |
||||||
|
} |
||||||
|
|
||||||
|
this.writer.Write(text); |
||||||
|
} |
||||||
|
|
||||||
|
private void WriteEncodedJsonValue(JsonValue value) |
||||||
|
{ |
||||||
|
switch (value.Type) { |
||||||
|
case JsonValueType.Null: |
||||||
|
this.Write("null"); |
||||||
|
break; |
||||||
|
|
||||||
|
case JsonValueType.Boolean: |
||||||
|
this.Write(value.AsString); |
||||||
|
break; |
||||||
|
|
||||||
|
case JsonValueType.Number: |
||||||
|
this.Write(((double)value).ToString(CultureInfo.InvariantCulture)); |
||||||
|
break; |
||||||
|
|
||||||
|
default: |
||||||
|
Debug.Assert(value.Type == JsonValueType.String, "value.Type == JsonValueType.String"); |
||||||
|
this.WriteEncodedString((string)value); |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private void WriteEncodedString(string text) |
||||||
|
{ |
||||||
|
this.Write("\""); |
||||||
|
|
||||||
|
for (int i = 0; i < text.Length; i += 1) { |
||||||
|
var currentChar = text[i]; |
||||||
|
|
||||||
|
// Encoding special characters.
|
||||||
|
switch (currentChar) { |
||||||
|
case '\\': |
||||||
|
this.writer.Write("\\\\"); |
||||||
|
break; |
||||||
|
|
||||||
|
case '\"': |
||||||
|
this.writer.Write("\\\""); |
||||||
|
break; |
||||||
|
|
||||||
|
case '/': |
||||||
|
this.writer.Write("\\/"); |
||||||
|
break; |
||||||
|
|
||||||
|
case '\b': |
||||||
|
this.writer.Write("\\b"); |
||||||
|
break; |
||||||
|
|
||||||
|
case '\f': |
||||||
|
this.writer.Write("\\f"); |
||||||
|
break; |
||||||
|
|
||||||
|
case '\n': |
||||||
|
this.writer.Write("\\n"); |
||||||
|
break; |
||||||
|
|
||||||
|
case '\r': |
||||||
|
this.writer.Write("\\r"); |
||||||
|
break; |
||||||
|
|
||||||
|
case '\t': |
||||||
|
this.writer.Write("\\t"); |
||||||
|
break; |
||||||
|
|
||||||
|
default: |
||||||
|
this.writer.Write(currentChar); |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
this.writer.Write("\""); |
||||||
|
} |
||||||
|
|
||||||
|
private void WriteIndentation() |
||||||
|
{ |
||||||
|
for (var i = 0; i < this.indent; i += 1) { |
||||||
|
this.Write(this.IndentString); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private void WriteSpacing() |
||||||
|
{ |
||||||
|
this.Write(this.SpacingString); |
||||||
|
} |
||||||
|
|
||||||
|
private void WriteLine() |
||||||
|
{ |
||||||
|
this.Write(this.NewLineString); |
||||||
|
this.isNewLine = true; |
||||||
|
} |
||||||
|
|
||||||
|
private void WriteLine(string line) |
||||||
|
{ |
||||||
|
this.Write(line); |
||||||
|
this.WriteLine(); |
||||||
|
} |
||||||
|
|
||||||
|
private void AddRenderingCollection(IEnumerable<JsonValue> value) |
||||||
|
{ |
||||||
|
if (!this.renderingCollections.Add(value)) { |
||||||
|
throw new JsonSerializationException(ErrorType.CircularReference); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private void RemoveRenderingCollection(IEnumerable<JsonValue> value) |
||||||
|
{ |
||||||
|
this.renderingCollections.Remove(value); |
||||||
|
} |
||||||
|
|
||||||
|
private void Render(JsonValue value) |
||||||
|
{ |
||||||
|
switch (value.Type) { |
||||||
|
case JsonValueType.Null: |
||||||
|
case JsonValueType.Boolean: |
||||||
|
case JsonValueType.Number: |
||||||
|
case JsonValueType.String: |
||||||
|
this.WriteEncodedJsonValue(value); |
||||||
|
break; |
||||||
|
|
||||||
|
case JsonValueType.Object: |
||||||
|
this.Render((JsonObject)value); |
||||||
|
break; |
||||||
|
|
||||||
|
case JsonValueType.Array: |
||||||
|
this.Render((JsonArray)value); |
||||||
|
break; |
||||||
|
|
||||||
|
default: |
||||||
|
throw new JsonSerializationException(ErrorType.InvalidValueType); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private void Render(JsonArray value) |
||||||
|
{ |
||||||
|
this.AddRenderingCollection(value); |
||||||
|
|
||||||
|
this.WriteLine("["); |
||||||
|
|
||||||
|
this.indent += 1; |
||||||
|
|
||||||
|
using (var enumerator = value.GetEnumerator()) { |
||||||
|
var hasNext = enumerator.MoveNext(); |
||||||
|
|
||||||
|
while (hasNext) { |
||||||
|
this.Render(enumerator.Current); |
||||||
|
|
||||||
|
hasNext = enumerator.MoveNext(); |
||||||
|
|
||||||
|
if (hasNext) { |
||||||
|
this.WriteLine(","); |
||||||
|
} else { |
||||||
|
this.WriteLine(); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
this.indent -= 1; |
||||||
|
|
||||||
|
this.Write("]"); |
||||||
|
|
||||||
|
this.RemoveRenderingCollection(value); |
||||||
|
} |
||||||
|
|
||||||
|
private void Render(JsonObject value) |
||||||
|
{ |
||||||
|
this.AddRenderingCollection(value); |
||||||
|
|
||||||
|
this.WriteLine("{"); |
||||||
|
|
||||||
|
this.indent += 1; |
||||||
|
|
||||||
|
using (var enumerator = this.GetJsonObjectEnumerator(value)) { |
||||||
|
var hasNext = enumerator.MoveNext(); |
||||||
|
|
||||||
|
while (hasNext) { |
||||||
|
this.WriteEncodedString(enumerator.Current.Key); |
||||||
|
this.Write(":"); |
||||||
|
this.WriteSpacing(); |
||||||
|
this.Render(enumerator.Current.Value); |
||||||
|
|
||||||
|
hasNext = enumerator.MoveNext(); |
||||||
|
|
||||||
|
if (hasNext) { |
||||||
|
this.WriteLine(","); |
||||||
|
} else { |
||||||
|
this.WriteLine(); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
this.indent -= 1; |
||||||
|
|
||||||
|
this.Write("}"); |
||||||
|
|
||||||
|
this.RemoveRenderingCollection(value); |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets an JsonObject enumerator based on the configuration of this JsonWriter.
|
||||||
|
/// If JsonWriter.SortObjects is set to true, then a ordered enumerator is returned.
|
||||||
|
/// Otherwise, a faster non-deterministic enumerator is returned.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="jsonObject">The JsonObject for which to get an enumerator.</param>
|
||||||
|
/// <returns>An enumerator for the properties in a <see cref="JsonObject"/>.</returns>
|
||||||
|
private IEnumerator<KeyValuePair<string, JsonValue>> GetJsonObjectEnumerator(JsonObject jsonObject) |
||||||
|
{ |
||||||
|
if (this.SortObjects) { |
||||||
|
var sortedDictionary = new SortedDictionary<string, JsonValue>(StringComparer.Ordinal); |
||||||
|
|
||||||
|
foreach (var item in jsonObject) { |
||||||
|
sortedDictionary.Add(item.Key, item.Value); |
||||||
|
} |
||||||
|
|
||||||
|
return sortedDictionary.GetEnumerator(); |
||||||
|
} else { |
||||||
|
return jsonObject.GetEnumerator(); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,21 @@ |
|||||||
|
// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
|
||||||
|
|
||||||
|
namespace LightJson.Serialization |
||||||
|
{ |
||||||
|
/// <summary>
|
||||||
|
/// Represents a position within a plain text resource.
|
||||||
|
/// </summary>
|
||||||
|
internal struct TextPosition |
||||||
|
{ |
||||||
|
/// <summary>
|
||||||
|
/// The column position, 0-based.
|
||||||
|
/// </summary>
|
||||||
|
public long Column; |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The line position, 0-based.
|
||||||
|
/// </summary>
|
||||||
|
public long Line; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,212 @@ |
|||||||
|
// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
|
||||||
|
|
||||||
|
namespace LightJson.Serialization |
||||||
|
{ |
||||||
|
using System.IO; |
||||||
|
using ErrorType = JsonParseException.ErrorType; |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a text scanner that reads one character at a time.
|
||||||
|
/// </summary>
|
||||||
|
internal sealed class TextScanner |
||||||
|
{ |
||||||
|
private TextReader reader; |
||||||
|
private TextPosition position; |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="TextScanner"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="reader">The TextReader to read the text.</param>
|
||||||
|
public TextScanner(TextReader reader) |
||||||
|
{ |
||||||
|
this.reader = reader; |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the position of the scanner within the text.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The position of the scanner within the text.</value>
|
||||||
|
public TextPosition Position { |
||||||
|
get { |
||||||
|
return this.position; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reads the next character in the stream without changing the current position.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>The next character in the stream.</returns>
|
||||||
|
public char Peek() |
||||||
|
=> (char)this.Peek(throwAtEndOfFile: true); |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reads the next character in the stream without changing the current position.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="throwAtEndOfFile"><see langword="true"/> to throw an exception if the end of the file is
|
||||||
|
/// reached; otherwise, <see langword="false"/>.</param>
|
||||||
|
/// <returns>The next character in the stream, or -1 if the end of the file is reached with
|
||||||
|
/// <paramref name="throwAtEndOfFile"/> set to <see langword="false"/>.</returns>
|
||||||
|
public int Peek(bool throwAtEndOfFile) |
||||||
|
{ |
||||||
|
var next = this.reader.Peek(); |
||||||
|
|
||||||
|
if (next == -1 && throwAtEndOfFile) { |
||||||
|
throw new JsonParseException( |
||||||
|
ErrorType.IncompleteMessage, |
||||||
|
this.position); |
||||||
|
} else { |
||||||
|
return next; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reads the next character in the stream, advancing the text position.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>The next character in the stream.</returns>
|
||||||
|
public char Read() |
||||||
|
{ |
||||||
|
var next = this.reader.Read(); |
||||||
|
|
||||||
|
if (next == -1) { |
||||||
|
throw new JsonParseException( |
||||||
|
ErrorType.IncompleteMessage, |
||||||
|
this.position); |
||||||
|
} else { |
||||||
|
if (next == '\n') { |
||||||
|
this.position.Line += 1; |
||||||
|
this.position.Column = 0; |
||||||
|
} else { |
||||||
|
this.position.Column += 1; |
||||||
|
} |
||||||
|
|
||||||
|
return (char)next; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Advances the scanner to next non-whitespace character.
|
||||||
|
/// </summary>
|
||||||
|
public void SkipWhitespace() |
||||||
|
{ |
||||||
|
while (true) { |
||||||
|
char next = this.Peek(); |
||||||
|
if (char.IsWhiteSpace(next)) { |
||||||
|
this.Read(); |
||||||
|
continue; |
||||||
|
} else if (next == '/') { |
||||||
|
this.SkipComment(); |
||||||
|
continue; |
||||||
|
} else { |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Verifies that the given character matches the next character in the stream.
|
||||||
|
/// If the characters do not match, an exception will be thrown.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="next">The expected character.</param>
|
||||||
|
public void Assert(char next) |
||||||
|
{ |
||||||
|
var errorPosition = this.position; |
||||||
|
if (this.Read() != next) { |
||||||
|
throw new JsonParseException( |
||||||
|
string.Format("Parser expected '{0}'", next), |
||||||
|
ErrorType.InvalidOrUnexpectedCharacter, |
||||||
|
errorPosition); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Verifies that the given string matches the next characters in the stream.
|
||||||
|
/// If the strings do not match, an exception will be thrown.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="next">The expected string.</param>
|
||||||
|
public void Assert(string next) |
||||||
|
{ |
||||||
|
for (var i = 0; i < next.Length; i += 1) { |
||||||
|
this.Assert(next[i]); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private void SkipComment() |
||||||
|
{ |
||||||
|
// First character is the first slash
|
||||||
|
this.Read(); |
||||||
|
switch (this.Peek()) { |
||||||
|
case '/': |
||||||
|
this.SkipLineComment(); |
||||||
|
return; |
||||||
|
|
||||||
|
case '*': |
||||||
|
this.SkipBlockComment(); |
||||||
|
return; |
||||||
|
|
||||||
|
default: |
||||||
|
throw new JsonParseException( |
||||||
|
string.Format("Parser expected '{0}'", this.Peek()), |
||||||
|
ErrorType.InvalidOrUnexpectedCharacter, |
||||||
|
this.position); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private void SkipLineComment() |
||||||
|
{ |
||||||
|
// First character is the second '/' of the opening '//'
|
||||||
|
this.Read(); |
||||||
|
|
||||||
|
while (true) { |
||||||
|
switch (this.reader.Peek()) { |
||||||
|
case '\n': |
||||||
|
// Reached the end of the line
|
||||||
|
this.Read(); |
||||||
|
return; |
||||||
|
|
||||||
|
case -1: |
||||||
|
// Reached the end of the file
|
||||||
|
return; |
||||||
|
|
||||||
|
default: |
||||||
|
this.Read(); |
||||||
|
continue; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private void SkipBlockComment() |
||||||
|
{ |
||||||
|
// First character is the '*' of the opening '/*'
|
||||||
|
this.Read(); |
||||||
|
|
||||||
|
bool foundStar = false; |
||||||
|
while (true) { |
||||||
|
switch (this.reader.Peek()) { |
||||||
|
case '*': |
||||||
|
this.Read(); |
||||||
|
foundStar = true; |
||||||
|
continue; |
||||||
|
|
||||||
|
case '/': |
||||||
|
this.Read(); |
||||||
|
if (foundStar) { |
||||||
|
return; |
||||||
|
} else { |
||||||
|
foundStar = false; |
||||||
|
continue; |
||||||
|
} |
||||||
|
|
||||||
|
case -1: |
||||||
|
// Reached the end of the file
|
||||||
|
return; |
||||||
|
|
||||||
|
default: |
||||||
|
this.Read(); |
||||||
|
foundStar = false; |
||||||
|
continue; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue