blob: 423196d8556d99a774df8f01e68a44a9572b9c57 [file] [log] [blame]
using System;
using System.Collections.Generic;
using System.IO;
using System.Xml;
namespace Google.ProtocolBuffers.Serialization
{
/// <summary>
/// JsonFormatReader is used to parse Json into a message or an array of messages
/// </summary>
public class JsonFormatReader : AbstractTextReader
{
private readonly JsonCursor _input;
// The expected token that ends the current item, either ']' or '}'
private readonly Stack<int> _stopChar;
private enum ReaderState
{
Start,
BeginValue,
EndValue,
BeginObject,
BeginArray
}
private string _current;
private ReaderState _state;
/// <summary>
/// Constructs a JsonFormatReader to parse Json into a message, this method does not use text encoding, all bytes MUST
/// represent ASCII character values.
/// </summary>
public static JsonFormatReader CreateInstance(Stream stream)
{
return new JsonFormatReader(JsonCursor.CreateInstance(stream));
}
/// <summary>
/// Constructs a JsonFormatReader to parse Json into a message, this method does not use text encoding, all bytes MUST
/// represent ASCII character values.
/// </summary>
public static JsonFormatReader CreateInstance(byte[] bytes)
{
return new JsonFormatReader(JsonCursor.CreateInstance(bytes));
}
/// <summary>
/// Constructs a JsonFormatReader to parse Json into a message
/// </summary>
public static JsonFormatReader CreateInstance(string jsonText)
{
return new JsonFormatReader(JsonCursor.CreateInstance(jsonText));
}
/// <summary>
/// Constructs a JsonFormatReader to parse Json into a message
/// </summary>
public static JsonFormatReader CreateInstance(TextReader input)
{
return new JsonFormatReader(JsonCursor.CreateInstance(input));
}
/// <summary>
/// Constructs a JsonFormatReader to parse Json into a message
/// </summary>
internal JsonFormatReader(JsonCursor input)
{
_input = input;
_stopChar = new Stack<int>();
_stopChar.Push(-1);
_state = ReaderState.Start;
}
/// <summary>
/// Constructs a JsonFormatReader to parse Json into a message
/// </summary>
protected JsonFormatReader(TextReader input)
: this(JsonCursor.CreateInstance(input))
{
}
/// <summary>
/// Returns true if the reader is currently on an array element
/// </summary>
public bool IsArrayMessage
{
get { return _input.NextChar == '['; }
}
/// <summary>
/// Returns an enumerator that is used to cursor over an array of messages
/// </summary>
/// <remarks>
/// This is generally used when receiving an array of messages rather than a single root message
/// </remarks>
public IEnumerable<JsonFormatReader> EnumerateArray()
{
foreach (string ignored in ForeachArrayItem(_current))
{
yield return this;
}
}
/// <summary>
/// Reads the root-message preamble specific to this formatter
/// </summary>
public override void ReadMessageStart()
{
_input.Consume('{');
_stopChar.Push('}');
_state = ReaderState.BeginObject;
}
/// <summary>
/// Reads the root-message close specific to this formatter
/// </summary>
public override void ReadMessageEnd()
{
_input.Consume((char)_stopChar.Pop());
_state = ReaderState.EndValue;
}
/// <summary>
/// Merges the contents of stream into the provided message builder
/// </summary>
public override TBuilder Merge<TBuilder>(TBuilder builder, ExtensionRegistry registry)
{
ReadMessageStart();
builder.WeakMergeFrom(this, registry);
ReadMessageEnd();
return builder;
}
/// <summary>
/// Causes the reader to skip past this field
/// </summary>
protected override void Skip()
{
object temp;
_input.ReadVariant(out temp);
_state = ReaderState.EndValue;
}
/// <summary>
/// Peeks at the next field in the input stream and returns what information is available.
/// </summary>
/// <remarks>
/// This may be called multiple times without actually reading the field. Only after the field
/// is either read, or skipped, should PeekNext return a different value.
/// </remarks>
protected override bool PeekNext(out string field)
{
field = _current;
if (_state == ReaderState.BeginValue)
{
return true;
}
int next = _input.NextChar;
if (next == _stopChar.Peek())
{
return false;
}
_input.Assert(next != -1, "Unexpected end of file.");
//not sure about this yet, it will allow {, "a":true }
if (_state == ReaderState.EndValue && !_input.TryConsume(','))
{
return false;
}
field = _current = _input.ReadString();
_input.Consume(':');
_state = ReaderState.BeginValue;
return true;
}
/// <summary>
/// Returns true if it was able to read a String from the input
/// </summary>
protected override bool ReadAsText(ref string value, Type typeInfo)
{
object temp;
JsonCursor.JsType type = _input.ReadVariant(out temp);
_state = ReaderState.EndValue;
_input.Assert(type != JsonCursor.JsType.Array && type != JsonCursor.JsType.Object,
"Encountered {0} while expecting {1}", type, typeInfo);
if (type == JsonCursor.JsType.Null)
{
return false;
}
if (type == JsonCursor.JsType.True)
{
value = "1";
}
else if (type == JsonCursor.JsType.False)
{
value = "0";
}
else
{
value = temp as string;
}
//exponent representation of integer number:
if (value != null && type == JsonCursor.JsType.Number &&
(typeInfo != typeof(double) && typeInfo != typeof(float)) &&
value.IndexOf("e", StringComparison.OrdinalIgnoreCase) > 0)
{
value = XmlConvert.ToString((long) Math.Round(XmlConvert.ToDouble(value), 0));
}
return value != null;
}
/// <summary>
/// Returns true if it was able to read a ByteString from the input
/// </summary>
protected override bool Read(ref ByteString value)
{
string bytes = null;
if (Read(ref bytes))
{
value = ByteString.FromBase64(bytes);
return true;
}
return false;
}
/// <summary>
/// Cursors through the array elements and stops at the end of the array
/// </summary>
protected override IEnumerable<string> ForeachArrayItem(string field)
{
_input.Consume('[');
_stopChar.Push(']');
_state = ReaderState.BeginArray;
while (_input.NextChar != ']')
{
_current = field;
yield return field;
if (!_input.TryConsume(','))
{
break;
}
}
_input.Consume((char) _stopChar.Pop());
_state = ReaderState.EndValue;
}
/// <summary>
/// Merges the input stream into the provided IBuilderLite
/// </summary>
protected override bool ReadMessage(IBuilderLite builder, ExtensionRegistry registry)
{
Merge(builder, registry);
return true;
}
}
}