mirror of https://github.com/icsharpcode/ILSpy.git
12 changed files with 2454 additions and 11 deletions
@ -0,0 +1,181 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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