// JValue - JNumber using System; using System.Globalization; using Leguar.TotalJSON.Internal; namespace Leguar.TotalJSON { /// /// Class to store number value in JSON format. Once JNumber instance is created, its value can't be changed. /// /// There is no limit how long or big numbers can be, but trying to read too big number for example as long will cause exception. /// Oversized numbers can be still read out and handled as strings. /// public class JNumber : JValue { private readonly string valueAsString; /// /// Creates new JSON number value from string. There is no limits in number size as long as it follows json number format. /// /// /// Value for this JNumber object in string format. /// /// /// If parameter string is not valid number. /// public JNumber(string numberAsString) : base() { string errorMessage=numberFormatCheck(numberAsString); if (errorMessage!=null) { throw (new JArgumentException("Parameter \""+numberAsString+"\" is not valid JSON format number: "+errorMessage,"numberAsString")); } valueAsString = numberAsString; } /// /// Creates new JSON number value from c# int value. /// /// /// Value for this JNumber object. /// public JNumber(int numberAsInt) : base() { valueAsString=safeLongAsString(numberAsInt); } /// /// Creates new JSON number value from c# long value. /// /// /// Value for this JNumber object. /// public JNumber(long numberAsLong) : base() { valueAsString=safeLongAsString(numberAsLong); } /// /// Creates new JSON number value from c# float value. /// /// /// Value for this JNumber object. /// /// /// If parameter is NaN or Infinity. /// public JNumber(float numberAsFloat) : base() { if (float.IsNaN(numberAsFloat)) { throw (new JArgumentException("Can not create new JNumber from float that is NaN","numberAsFloat")); } if (float.IsPositiveInfinity(numberAsFloat)) { throw (new JArgumentException("Can not create new JNumber from float that is infinity","numberAsFloat")); } if (float.IsNegativeInfinity(numberAsFloat)) { throw (new JArgumentException("Can not create new JNumber from float that is negative infinity","numberAsFloat")); } valueAsString=safeFloatAsString(numberAsFloat); } /// /// Creates new JSON number value from c# double value. /// /// /// Value for this JNumber object. /// /// /// If parameter is NaN or Infinity. /// public JNumber(double numberAsDouble) : base() { if (double.IsNaN(numberAsDouble)) { throw (new JArgumentException("Can not create new JNumber from double that is NaN","numberAsDouble")); } if (double.IsPositiveInfinity(numberAsDouble)) { throw (new JArgumentException("Can not create new JNumber from double that is infinity","numberAsDouble")); } if (double.IsNegativeInfinity(numberAsDouble)) { throw (new JArgumentException("Can not create new JNumber from double that is negative infinity","numberAsDouble")); } valueAsString=safeDoubleAsString(numberAsDouble); } /// /// Creates new JSON number value from c# decimal value. /// /// /// Value for this JNumber object. /// public JNumber(decimal numberAsDecimal) : base() { valueAsString=addDecimalPoint(numberAsDecimal.ToString(CultureInfo.InvariantCulture)); } private JNumber(string safeValueAsString, ParseStringRunner parseStringRunner) : base() { valueAsString=parseStringRunner.getPossiblyFixedNumber(safeValueAsString); } /// /// Returns compact information of this JNumber object as string, for debug purposes. /// /// /// Single string with information of this JNumber object. /// public override string ToString() { return ("[JNumber: "+valueAsString+"]"); } /// /// Test if another object equals to this object. Always returns false if parameter object is null or it is not instance of JNumber. /// Two JNumber objects are equal if both contains value which string representation is exactly equal. /// For example JNumber that contains "1" is not equal to JNumber that contains "1.0" /// /// /// Another object that is compared to this one. /// /// /// True if objects are equal, false otherwise. /// public override bool Equals(object anotherObject) { if (anotherObject==null) { return false; } if (!(anotherObject is JNumber)) { return false; } JNumber anotherJNumber=(JNumber)(anotherObject); return (valueAsString.Equals(anotherJNumber.AsString())); } public override int GetHashCode() { return valueAsString.GetHashCode(); } /// /// Gets value of this number object as long. This will throw exception if number is floating point number or outside long range. /// /// /// Value as long. /// /// /// If number stored to this JNumber doesn't fit in long. /// /// /// If number stored to this JNumber is floating point number. /// public long AsLong() { try { return long.Parse(valueAsString,CultureInfo.InvariantCulture); } catch (OverflowException) { throw (new JNumberOverflowException(valueAsString,"long",""+long.MinValue,""+long.MaxValue)); } catch (FormatException) { throw (new JNumberFormatException(valueAsString,"long")); } } /// /// Gets value of this number object as unsigned long. This will throw exception if number is floating point number or outside ulong range. /// /// /// Value as ulong. /// /// /// If number stored to this JNumber doesn't fit in ulong. /// /// /// If number stored to this JNumber is floating point number. /// public ulong AsULong() { try { return ulong.Parse(valueAsString,CultureInfo.InvariantCulture); } catch (OverflowException) { throw (new JNumberOverflowException(valueAsString,"ulong",""+ulong.MinValue,""+ulong.MaxValue)); } catch (FormatException) { throw (new JNumberFormatException(valueAsString,"ulong")); } } /// /// Gets value of this number object as int. This will throw exception if number is floating point number or outside int range. /// /// /// Value as int. /// /// /// If number stored to this JNumber doesn't fit in int. /// /// /// If number stored to this JNumber is floating point number. /// public int AsInt() { try { return int.Parse(valueAsString,CultureInfo.InvariantCulture); } catch (OverflowException) { throw (new JNumberOverflowException(valueAsString,"int",""+int.MinValue,""+int.MaxValue)); } catch (FormatException) { throw (new JNumberFormatException(valueAsString,"int")); } } /// /// Gets value of this number object as unsigned int. This will throw exception if number is floating point number or outside uint range. /// /// /// Value as uint. /// /// /// If number stored to this JNumber doesn't fit in uint. /// /// /// If number stored to this JNumber is floating point number. /// public uint AsUInt() { try { return uint.Parse(valueAsString,CultureInfo.InvariantCulture); } catch (OverflowException) { throw (new JNumberOverflowException(valueAsString,"uint",""+uint.MinValue,""+uint.MaxValue)); } catch (FormatException) { throw (new JNumberFormatException(valueAsString,"uint")); } } /// /// Gets value of this number object as short. This will throw exception if number is floating point number or outside short range. /// /// /// Value as short. /// /// /// If number stored to this JNumber doesn't fit in short. /// /// /// If number stored to this JNumber is floating point number. /// public short AsShort() { try { return short.Parse(valueAsString,CultureInfo.InvariantCulture); } catch (OverflowException) { throw (new JNumberOverflowException(valueAsString,"short",""+short.MinValue,""+short.MaxValue)); } catch (FormatException) { throw (new JNumberFormatException(valueAsString,"short")); } } /// /// Gets value of this number object as unsigned short. This will throw exception if number is floating point number or outside ushort range. /// /// /// Value as ushort. /// /// /// If number stored to this JNumber doesn't fit in ushort. /// /// /// If number stored to this JNumber is floating point number. /// public ushort AsUShort() { try { return ushort.Parse(valueAsString,CultureInfo.InvariantCulture); } catch (OverflowException) { throw (new JNumberOverflowException(valueAsString,"ushort",""+ushort.MinValue,""+ushort.MaxValue)); } catch (FormatException) { throw (new JNumberFormatException(valueAsString,"ushort")); } } /// /// Gets value of this number object as byte. This will throw exception if number is floating point number or outside byte range. /// /// /// Value as byte. /// /// /// If number stored to this JNumber doesn't fit in byte. /// /// /// If number stored to this JNumber is floating point number. /// public byte AsByte() { try { return byte.Parse(valueAsString,CultureInfo.InvariantCulture); } catch (OverflowException) { throw (new JNumberOverflowException(valueAsString,"byte",""+byte.MinValue,""+byte.MaxValue)); } catch (FormatException) { throw (new JNumberFormatException(valueAsString,"byte")); } } /// /// Gets value of this number object as signed byte. This will throw exception if number is floating point number or outside sbyte range. /// /// /// Value as sbyte. /// /// /// If number stored to this JNumber doesn't fit in sbyte. /// /// /// If number stored to this JNumber is floating point number. /// public sbyte AsSByte() { try { return sbyte.Parse(valueAsString,CultureInfo.InvariantCulture); } catch (OverflowException) { throw (new JNumberOverflowException(valueAsString,"sbyte",""+sbyte.MinValue,""+sbyte.MaxValue)); } catch (FormatException) { throw (new JNumberFormatException(valueAsString,"sbyte")); } } /// /// Gets value of this number object as double. This will throw exception if number is outside double range. /// /// /// Value as double. /// /// /// If number stored to this JNumber doesn't fit in double. /// public double AsDouble() { try { double value=double.Parse(valueAsString,CultureInfo.InvariantCulture); if (double.IsInfinity(value)) { throw (new OverflowException()); } return value; } catch (OverflowException) { throw (new JNumberOverflowException(valueAsString,"double",""+double.MinValue,""+double.MaxValue)); } } /// /// Gets value of this number object as float. This will throw exception if number is outside float range. /// /// /// Value as float. /// /// /// If number stored to this JNumber doesn't fit in float. /// public float AsFloat() { try { float value=float.Parse(valueAsString,CultureInfo.InvariantCulture); if (float.IsInfinity(value)) { throw (new OverflowException()); } return value; } catch (OverflowException) { throw (new JNumberOverflowException(valueAsString,"float",""+float.MinValue,""+float.MaxValue)); } } /// /// Gets value of this number object as decimal. This will throw exception if number is outside decimal range or number contains E/e notation. /// /// /// Value as decimal. /// /// /// If number stored to this JNumber doesn't fit in decimal. /// /// /// If number stored to this JNumber uses E/e notation (like 1.234567e89) /// public decimal AsDecimal() { try { return decimal.Parse(valueAsString,CultureInfo.InvariantCulture); } catch (OverflowException) { throw (new JNumberOverflowException(valueAsString,"decimal",""+decimal.MinValue,""+decimal.MaxValue)); } catch (FormatException) { throw (new JNumberFormatException(valueAsString)); } } /// /// Gets value of this number as string. /// /// /// Value as string. /// public string AsString() { return valueAsString; } /// /// Gets value of this number as object. First fitting value of these are returned: int, long, float, double /// /// /// If number stored to this JNumber doesn't fit in double. /// /// /// Value as object, that may be one of the 4 basic number objects. /// public object AsObject() { int iValue; if (int.TryParse(valueAsString, NumberStyles.Integer, CultureInfo.InvariantCulture, out iValue)) { return iValue; } long lValue; if (long.TryParse(valueAsString, NumberStyles.Integer, CultureInfo.InvariantCulture, out lValue)) { return lValue; } float fValue; if (float.TryParse(valueAsString, NumberStyles.Float, CultureInfo.InvariantCulture, out fValue)) { if (!float.IsInfinity(fValue)) { return fValue; } } // This could fail too in extreme cases, but custom exception is thrown return this.AsDouble(); } internal override void zCreate(CreateStringRunner createStringRunner) { createStringRunner.appendColoring(CreateStringRunner.COLOR_NUMBER); createStringRunner.append(this.AsString()); createStringRunner.appendColoring(CreateStringRunner.COLOR_END); } internal static JNumber zParse(ParseStringRunner parseStringRunner, char firstChr) { StringPointer sp = parseStringRunner.getStringPointer(); int start=sp.getCurrentIndex()-1; // -1 since first character is already read bool valid=parseCheck(sp,firstChr); if (!valid) { throw ParseException.forInvalidCharacter("Invalid number value \""+sp.getSubStringStartingFrom(start)+"\"",parseStringRunner); } else { sp.stepBack(); string validNumber=sp.getSubStringStartingFrom(start); return (new JNumber(validNumber,parseStringRunner)); } } private static bool parseCheck(StringPointer sp, char chr) { int state; if (chr=='-') { chr=sp.getNextChar(); } if (chr=='0') { chr=sp.getNextChar(); if (chr=='.') { state=5; } else if (chr=='e' || chr=='E') { state=7; } else { return true; // (-)0 } } else if (chr>='1' && chr<='9') { state=4; } else { return false; } do { chr=sp.getNextChar(); if (state==4) { if (chr>='0' && chr<='9') { state=4; } else if (chr=='.') { state=5; } else if (chr=='e' || chr=='E') { state=7; } else { return true; // (-)## } } else if (state==5) { if (chr>='0' && chr<='9') { state=6; } else { return false; } } else if (state==6) { if (chr>='0' && chr<='9') { state=6; } else if (chr=='e' || chr=='E') { state=7; } else { return true; // (-)(##).## } } else if (state==7) { if (chr=='+' || chr=='-') { chr=sp.getNextChar(); if (chr>='0' && chr<='9') { state=9; } else { return false; } } else if (chr>='0' && chr<='9') { state=9; } else { return false; } } else if (state==9) { if (chr>='0' && chr<='9') { state=9; } else { return true; // (-)(##).##[e/E](+/-)## } } } while (true); } private static string numberFormatCheck(string str) { int count=str.Length, state=1; for (int index=0; index='1' && chr<='9') { state=4; } else { return ("Invalid first character "+InternalTools.logSafeCharacter(chr)+", should be digit or minus sign"); } } else if (state==2) { // First digit after minus if (chr=='0') { state=3; } else if (chr>='1' && chr<='9') { state=4; } else { return ("Expecting at least one digit after minus sign, got "+InternalTools.logSafeCharacter(chr)+" instead"); } } else if (state==3) { // Number started with (minus) zero, only decimal point or E can follow if (chr=='.') { state=5; } else if (chr=='e' || chr=='E') { state=7; } else { return ("Only decimal point or E/e can follow number starting with 0 or -0, got "+InternalTools.logSafeCharacter(chr)+" instead"); } } else if (state==4) { // Digits before decimal point or E if (chr=='.') { state=5; } else if (chr=='e' || chr=='E') { state=7; } else if (chr<'0' || chr>'9') { return ("Invalid character "+InternalTools.logSafeCharacter(chr)+" (before possible decimal point or E/e)"); } } else if (state==5) { // Need at least one digit after decimal point if (chr>='0' && chr<='9') { state=6; } else { return ("Need at least one digit after decimal point, got "+InternalTools.logSafeCharacter(chr)+" instead"); } } else if (state==6) { // Following digits after decimal point if (chr=='e' || chr=='E') { state=7; } else if (chr<'0' || chr>'9') { return ("Invalid character "+InternalTools.logSafeCharacter(chr)+" after decimal point"); } } else if (state==7) { // Plus, minus or digit after E if (chr=='+' || chr=='-') { state=8; } else if (chr>='0' && chr<='9') { state=9; } else { return ("Expecting digit or plus/minus sign after E/e, got "+InternalTools.logSafeCharacter(chr)+" instead"); } } else if (state==8) { // Need at least one digit after E plus/minus, if (chr>='0' && chr<='9') { state=9; } else { return ("Expecting digit after plus/minus sign after E/e, got "+InternalTools.logSafeCharacter(chr)+" instead"); } } else if (state==9) { // Following digits after E if (chr<'0' || chr>'9') { return ("Invalid character "+InternalTools.logSafeCharacter(chr)+" in digits after E/e"); } } } if (state==1) { return "String is empty"; } if (state==2) { return "String contains only minus sign"; } if (state==5) { return "No digits after decimal point"; } if (state==7) { return "Need at least one digit after E/e"; } if (state==8) { return "Need at least one digit after plus/minus sign after E/e"; } return null; } private string safeFloatAsString(float value) { // Basic change that doesn't add too many decimals string basicStr=value.ToString(CultureInfo.InvariantCulture); basicStr=addDecimalPoint(basicStr); try { float reverse=float.Parse(basicStr,CultureInfo.InvariantCulture); if (reverse.Equals(value)) { // May not be true because rounding return basicStr; } } catch (OverflowException) { // This happens because rounding if value is close min/max } // Try out with more decimals string rStr=value.ToString("R",CultureInfo.InvariantCulture); rStr=addDecimalPoint(rStr); try { float reverse=float.Parse(rStr,CultureInfo.InvariantCulture); if (reverse.Equals(value)) { return rStr; } } catch (Exception) { // Intentionally empty, falling back } // If neither works, fall back to simplest return basicStr; } private string safeDoubleAsString(double value) { // Basic change that doesn't add too many decimals string basicStr=value.ToString(CultureInfo.InvariantCulture); basicStr=addDecimalPoint(basicStr); try { double reverse=double.Parse(basicStr,CultureInfo.InvariantCulture); if (reverse.Equals(value)) { return basicStr; } } catch (OverflowException) { // This happens because rounding if value is close min/max } // Try out with more decimals string rStr=value.ToString("R",CultureInfo.InvariantCulture); rStr=addDecimalPoint(rStr); try { double reverse=double.Parse(rStr,CultureInfo.InvariantCulture); if (reverse.Equals(value)) { return rStr; } } catch (Exception) { // Intentionally empty, falling back } // If neither works, fall back to simplest return basicStr; } private static string addDecimalPoint(string str) { if (str.IndexOf('E')==-1 && str.IndexOf('e')==-1 && str.IndexOf('.')==-1) { str+=".0"; } return str; } private static string safeLongAsString(long value) { return value.ToString(CultureInfo.InvariantCulture); } internal override object zDeserialize(Type type, string toFieldName, DeserializeSettings deserializeSettings) { // In case type is nullable type, for example "int?" Type nullableType = Nullable.GetUnderlyingType(type); if (nullableType != null) { return this.zDeserialize(nullableType, toFieldName, deserializeSettings); } if (type.IsEnum) { if (deserializeSettings.AllowDeserializeIntsToEnums) { return this.AsInt(); } throw (DeserializeException.forNonMatchingEnumType(toFieldName)); } if (type==typeof(float)) { return this.AsFloat(); } if (type==typeof(double)) { return this.AsDouble(); } if (type==typeof(decimal)) { return this.AsDecimal(); } if (type==typeof(int)) { return this.AsInt(); } if (type==typeof(long)) { return this.AsLong(); } if (type==typeof(short)) { return this.AsShort(); } if (type==typeof(byte)) { return this.AsByte(); } if (type==typeof(uint)) { return this.AsUInt(); } if (type==typeof(ulong)) { return this.AsULong(); } if (type==typeof(ushort)) { return this.AsUShort(); } if (type==typeof(sbyte)) { return this.AsSByte(); } if (type==typeof(object)) { if (deserializeSettings.AllowFieldsToBeObjects) { return this.AsObject(); } throw (DeserializeException.forNonMatchingTypeObject(this, toFieldName)); } throw (DeserializeException.forNonMatchingType(this,type,toFieldName)); } } }