using System;
using System.Collections.Generic;
using System.IO;
using System.Xml;
namespace Google.ProtocolBuffers.Serialization
{
///
/// JsonFormatReader is used to parse Json into a message or an array of messages
///
public class JsonFormatReader : AbstractTextReader
{
private readonly JsonCursor _input;
private readonly Stack _stopChar;
private enum ReaderState
{
Start,
BeginValue,
EndValue,
BeginObject,
BeginArray
}
private string _current;
private ReaderState _state;
///
/// Constructs a JsonFormatReader to parse Json into a message, this method does not use text encoding, all bytes MUST
/// represent ASCII character values.
///
public static JsonFormatReader CreateInstance(Stream stream)
{
return new JsonFormatReader(JsonCursor.CreateInstance(stream));
}
///
/// Constructs a JsonFormatReader to parse Json into a message, this method does not use text encoding, all bytes MUST
/// represent ASCII character values.
///
public static JsonFormatReader CreateInstance(byte[] bytes)
{
return new JsonFormatReader(JsonCursor.CreateInstance(bytes));
}
///
/// Constructs a JsonFormatReader to parse Json into a message
///
public static JsonFormatReader CreateInstance(string jsonText)
{
return new JsonFormatReader(JsonCursor.CreateInstance(jsonText));
}
///
/// Constructs a JsonFormatReader to parse Json into a message
///
public static JsonFormatReader CreateInstance(TextReader input)
{
return new JsonFormatReader(JsonCursor.CreateInstance(input));
}
///
/// Constructs a JsonFormatReader to parse Json into a message
///
internal JsonFormatReader(JsonCursor input)
{
_input = input;
_stopChar = new Stack();
_stopChar.Push(-1);
_state = ReaderState.Start;
}
///
/// Constructs a JsonFormatReader to parse Json into a message
///
protected JsonFormatReader(TextReader input)
: this(JsonCursor.CreateInstance(input))
{
}
///
/// Returns true if the reader is currently on an array element
///
public bool IsArrayMessage
{
get { return _input.NextChar == '['; }
}
///
/// Returns an enumerator that is used to cursor over an array of messages
///
///
/// This is generally used when receiving an array of messages rather than a single root message
///
public IEnumerable EnumerateArray()
{
foreach (string ignored in ForeachArrayItem(_current))
{
yield return this;
}
}
///
/// Merges the contents of stream into the provided message builder
///
public override TBuilder Merge(TBuilder builder, ExtensionRegistry registry)
{
_input.Consume('{');
_stopChar.Push('}');
_state = ReaderState.BeginObject;
builder.WeakMergeFrom(this, registry);
_input.Consume((char) _stopChar.Pop());
_state = ReaderState.EndValue;
return builder;
}
///
/// Causes the reader to skip past this field
///
protected override void Skip()
{
object temp;
_input.ReadVariant(out temp);
_state = ReaderState.EndValue;
}
///
/// Peeks at the next field in the input stream and returns what information is available.
///
///
/// 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.
///
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;
}
///
/// Returns true if it was able to read a String from the input
///
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;
}
///
/// Returns true if it was able to read a ByteString from the input
///
protected override bool Read(ref ByteString value)
{
string bytes = null;
if (Read(ref bytes))
{
value = ByteString.FromBase64(bytes);
return true;
}
return false;
}
///
/// Cursors through the array elements and stops at the end of the array
///
protected override IEnumerable 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;
}
///
/// Merges the input stream into the provided IBuilderLite
///
protected override bool ReadMessage(IBuilderLite builder, ExtensionRegistry registry)
{
Merge(builder, registry);
return true;
}
}
}