2019-12-08 19:54:52 +01:00
#nullable enable
using System ;
2019-12-04 18:57:18 +01:00
using System.Collections.Generic ;
using System.Text ;
2019-12-08 19:54:52 +01:00
namespace Swan.Formatters {
/// <summary>
/// A very simple, light-weight JSON library written by Mario
/// to teach Geo how things are done
///
/// This is an useful helper for small tasks but it doesn't represent a full-featured
/// serializer such as the beloved Json.NET.
/// </summary>
public partial class Json {
2019-12-04 18:57:18 +01:00
/// <summary>
2019-12-08 19:54:52 +01:00
/// A simple JSON Deserializer.
2019-12-04 18:57:18 +01:00
/// </summary>
2019-12-08 19:54:52 +01:00
private class Deserializer {
#region State Variables
private readonly Object ? _result ;
private readonly String _json ;
private Dictionary < String , Object ? > ? _resultObject ;
private List < Object ? > ? _resultArray ;
private ReadState _state = ReadState . WaitingForRootOpen ;
private String ? _currentFieldName ;
private Int32 _index ;
#endregion
private Deserializer ( String ? json , Int32 startIndex ) {
if ( json = = null ) {
this . _json = "" ;
return ;
}
this . _json = json ;
for ( this . _index = startIndex ; this . _index < this . _json . Length ; this . _index + + ) {
switch ( this . _state ) {
case ReadState . WaitingForRootOpen :
this . WaitForRootOpen ( ) ;
continue ;
case ReadState . WaitingForField when Char . IsWhiteSpace ( this . _json , this . _index ) :
continue ;
case ReadState . WaitingForField when this . _resultObject ! = null & & this . _json [ this . _index ] = = CloseObjectChar | | this . _resultArray ! = null & & this . _json [ this . _index ] = = CloseArrayChar :
// Handle empty arrays and empty objects
this . _result = this . _resultObject ? ? this . _resultArray as Object ;
return ;
case ReadState . WaitingForField when this . _json [ this . _index ] ! = StringQuotedChar :
throw this . CreateParserException ( $"'{StringQuotedChar}'" ) ;
case ReadState . WaitingForField : {
Int32 charCount = this . GetFieldNameCount ( ) ;
this . _currentFieldName = Unescape ( this . _json . SliceLength ( this . _index + 1 , charCount ) ) ;
this . _index + = charCount + 1 ;
this . _state = ReadState . WaitingForColon ;
continue ;
}
case ReadState . WaitingForColon when Char . IsWhiteSpace ( this . _json , this . _index ) :
continue ;
case ReadState . WaitingForColon when this . _json [ this . _index ] ! = ValueSeparatorChar :
throw this . CreateParserException ( $"'{ValueSeparatorChar}'" ) ;
case ReadState . WaitingForColon :
this . _state = ReadState . WaitingForValue ;
continue ;
case ReadState . WaitingForValue when Char . IsWhiteSpace ( this . _json , this . _index ) :
continue ;
case ReadState . WaitingForValue when this . _resultObject ! = null & & this . _json [ this . _index ] = = CloseObjectChar | | this . _resultArray ! = null & & this . _json [ this . _index ] = = CloseArrayChar :
// Handle empty arrays and empty objects
this . _result = this . _resultObject ? ? this . _resultArray as Object ;
return ;
case ReadState . WaitingForValue :
this . ExtractValue ( ) ;
continue ;
}
if ( this . _state ! = ReadState . WaitingForNextOrRootClose | | Char . IsWhiteSpace ( this . _json , this . _index ) ) {
continue ;
}
if ( this . _json [ this . _index ] = = FieldSeparatorChar ) {
if ( this . _resultObject ! = null ) {
this . _state = ReadState . WaitingForField ;
this . _currentFieldName = null ;
continue ;
}
this . _state = ReadState . WaitingForValue ;
continue ;
}
if ( ( this . _resultObject = = null | | this . _json [ this . _index ] ! = CloseObjectChar ) & & ( this . _resultArray = = null | | this . _json [ this . _index ] ! = CloseArrayChar ) ) {
throw this . CreateParserException ( $"'{FieldSeparatorChar}' '{CloseObjectChar}' or '{CloseArrayChar}'" ) ;
}
this . _result = this . _resultObject ? ? this . _resultArray as Object ;
return ;
}
}
internal static Object ? DeserializeInternal ( String ? json ) = > new Deserializer ( json , 0 ) . _result ;
private void WaitForRootOpen ( ) {
if ( Char . IsWhiteSpace ( this . _json , this . _index ) ) {
return ;
}
switch ( this . _json [ this . _index ] ) {
case OpenObjectChar :
this . _resultObject = new Dictionary < String , Object ? > ( ) ;
this . _state = ReadState . WaitingForField ;
return ;
case OpenArrayChar :
this . _resultArray = new List < Object ? > ( ) ;
this . _state = ReadState . WaitingForValue ;
return ;
default :
throw this . CreateParserException ( $"'{OpenObjectChar}' or '{OpenArrayChar}'" ) ;
}
}
private void ExtractValue ( ) {
// determine the value based on what it starts with
switch ( this . _json [ this . _index ] ) {
case StringQuotedChar : // expect a string
this . ExtractStringQuoted ( ) ;
break ;
case OpenObjectChar : // expect object
case OpenArrayChar : // expect array
this . ExtractObject ( ) ;
break ;
case 't' : // expect true
this . ExtractConstant ( TrueLiteral , true ) ;
break ;
case 'f' : // expect false
this . ExtractConstant ( FalseLiteral , false ) ;
break ;
case 'n' : // expect null
this . ExtractConstant ( NullLiteral , null ) ;
break ;
default : // expect number
this . ExtractNumber ( ) ;
break ;
}
this . _currentFieldName = null ;
this . _state = ReadState . WaitingForNextOrRootClose ;
}
private static String Unescape ( String str ) {
// check if we need to unescape at all
if ( str . IndexOf ( StringEscapeChar ) < 0 ) {
return str ;
}
StringBuilder builder = new StringBuilder ( str . Length ) ;
for ( Int32 i = 0 ; i < str . Length ; i + + ) {
if ( str [ i ] ! = StringEscapeChar ) {
_ = builder . Append ( str [ i ] ) ;
continue ;
}
if ( i + 1 > str . Length - 1 ) {
break ;
}
// escape sequence begins here
switch ( str [ i + 1 ] ) {
case 'u' :
i = ExtractEscapeSequence ( str , i , builder ) ;
break ;
case 'b' :
_ = builder . Append ( '\b' ) ;
i + = 1 ;
break ;
case 't' :
_ = builder . Append ( '\t' ) ;
i + = 1 ;
break ;
case 'n' :
_ = builder . Append ( '\n' ) ;
i + = 1 ;
break ;
case 'f' :
_ = builder . Append ( '\f' ) ;
i + = 1 ;
break ;
case 'r' :
_ = builder . Append ( '\r' ) ;
i + = 1 ;
break ;
default :
_ = builder . Append ( str [ i + 1 ] ) ;
i + = 1 ;
break ;
}
}
return builder . ToString ( ) ;
}
private static Int32 ExtractEscapeSequence ( String str , Int32 i , StringBuilder builder ) {
Int32 startIndex = i + 2 ;
Int32 endIndex = i + 5 ;
if ( endIndex > str . Length - 1 ) {
_ = builder . Append ( str [ i + 1 ] ) ;
i + = 1 ;
return i ;
}
Byte [ ] hexCode = str . Slice ( startIndex , endIndex ) . ConvertHexadecimalToBytes ( ) ;
_ = builder . Append ( Encoding . BigEndianUnicode . GetChars ( hexCode ) ) ;
i + = 5 ;
return i ;
}
private Int32 GetFieldNameCount ( ) {
Int32 charCount = 0 ;
for ( Int32 j = this . _index + 1 ; j < this . _json . Length ; j + + ) {
if ( this . _json [ j ] = = StringQuotedChar & & this . _json [ j - 1 ] ! = StringEscapeChar ) {
break ;
}
charCount + + ;
}
return charCount ;
}
private void ExtractObject ( ) {
// Extract and set the value
Deserializer deserializer = new Deserializer ( this . _json , this . _index ) ;
if ( this . _currentFieldName ! = null ) {
this . _resultObject ! [ this . _currentFieldName ] = deserializer . _result ! ;
} else {
this . _resultArray ! . Add ( deserializer . _result ! ) ;
}
this . _index = deserializer . _index ;
}
private void ExtractNumber ( ) {
Int32 charCount = 0 ;
for ( Int32 j = this . _index ; j < this . _json . Length ; j + + ) {
if ( Char . IsWhiteSpace ( this . _json [ j ] ) | | this . _json [ j ] = = FieldSeparatorChar | | this . _resultObject ! = null & & this . _json [ j ] = = CloseObjectChar | | this . _resultArray ! = null & & this . _json [ j ] = = CloseArrayChar ) {
break ;
}
charCount + + ;
}
// Extract and set the value
String stringValue = this . _json . SliceLength ( this . _index , charCount ) ;
if ( Decimal . TryParse ( stringValue , System . Globalization . NumberStyles . Number , System . Globalization . CultureInfo . InvariantCulture , out Decimal value ) = = false ) {
throw this . CreateParserException ( "[number]" ) ;
}
if ( this . _currentFieldName ! = null ) {
this . _resultObject ! [ this . _currentFieldName ] = value ;
} else {
this . _resultArray ! . Add ( value ) ;
}
this . _index + = charCount - 1 ;
}
private void ExtractConstant ( String boolValue , Boolean ? value ) {
if ( this . _json . SliceLength ( this . _index , boolValue . Length ) ! = boolValue ) {
throw this . CreateParserException ( $"'{ValueSeparatorChar}'" ) ;
}
// Extract and set the value
if ( this . _currentFieldName ! = null ) {
this . _resultObject ! [ this . _currentFieldName ] = value ;
} else {
this . _resultArray ! . Add ( value ) ;
}
this . _index + = boolValue . Length - 1 ;
}
private void ExtractStringQuoted ( ) {
Int32 charCount = 0 ;
Boolean escapeCharFound = false ;
for ( Int32 j = this . _index + 1 ; j < this . _json . Length ; j + + ) {
if ( this . _json [ j ] = = StringQuotedChar & & ! escapeCharFound ) {
break ;
}
escapeCharFound = this . _json [ j ] = = StringEscapeChar & & ! escapeCharFound ;
charCount + + ;
}
// Extract and set the value
String value = Unescape ( this . _json . SliceLength ( this . _index + 1 , charCount ) ) ;
if ( this . _currentFieldName ! = null ) {
this . _resultObject ! [ this . _currentFieldName ] = value ;
} else {
this . _resultArray ! . Add ( value ) ;
}
this . _index + = charCount + 1 ;
}
private FormatException CreateParserException ( String expected ) {
Tuple < Int32 , Int32 > textPosition = this . _json . TextPositionAt ( this . _index ) ;
return new FormatException ( $"Parser error (Line {textPosition.Item1}, Col {textPosition.Item2}, State {this._state}): Expected {expected} but got '{this._json[this._index]}'." ) ;
}
/// <summary>
/// Defines the different JSON read states.
/// </summary>
private enum ReadState {
WaitingForRootOpen ,
WaitingForField ,
WaitingForColon ,
WaitingForValue ,
WaitingForNextOrRootClose ,
}
}
}
2019-12-04 18:57:18 +01:00
}