JsonParser.cs 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Globalization;
  4. using System.Linq;
  5. using System.Text;
  6. using System.Text.RegularExpressions;
  7. namespace UnityEngine.InputSystem.Utilities
  8. {
  9. /// <summary>
  10. /// A JSON parser that instead of turning a string in JSON format into a
  11. /// C# object graph, allows navigating the source text directly.
  12. /// </summary>
  13. /// <remarks>
  14. /// This helper is most useful for avoiding a great many string and general object allocations
  15. /// that would happen when turning a JSON object into a C# object graph.
  16. /// </remarks>
  17. internal struct JsonParser
  18. {
  19. public JsonParser(string json)
  20. : this()
  21. {
  22. if (json == null)
  23. throw new ArgumentNullException(nameof(json));
  24. m_Text = json;
  25. m_Length = json.Length;
  26. }
  27. public void Reset()
  28. {
  29. m_Position = 0;
  30. m_MatchAnyElementInArray = false;
  31. m_DryRun = false;
  32. }
  33. public override string ToString()
  34. {
  35. if (m_Text != null)
  36. return $"{m_Position}: {m_Text.Substring(m_Position)}";
  37. return base.ToString();
  38. }
  39. /// <summary>
  40. /// Navigate to the given property.
  41. /// </summary>
  42. /// <param name="path"></param>
  43. /// <remarks>
  44. /// This navigates from the current property.
  45. /// </remarks>
  46. public bool NavigateToProperty(string path)
  47. {
  48. if (string.IsNullOrEmpty(path))
  49. throw new ArgumentNullException(nameof(path));
  50. var pathLength = path.Length;
  51. var pathPosition = 0;
  52. m_DryRun = true;
  53. if (!ParseToken('{'))
  54. return false;
  55. while (m_Position < m_Length && pathPosition < pathLength)
  56. {
  57. // Find start of property name.
  58. SkipWhitespace();
  59. if (m_Position == m_Length)
  60. return false;
  61. if (m_Text[m_Position] != '"')
  62. return false;
  63. ++m_Position;
  64. // Try to match single path component.
  65. var pathStartPosition = pathPosition;
  66. while (pathPosition < pathLength)
  67. {
  68. var ch = path[pathPosition];
  69. if (ch == '/' || ch == '[')
  70. break;
  71. if (m_Text[m_Position] != ch)
  72. break;
  73. ++m_Position;
  74. ++pathPosition;
  75. }
  76. // See if we have a match.
  77. if (m_Position < m_Length && m_Text[m_Position] == '"' && (pathPosition >= pathLength || path[pathPosition] == '/' || path[pathPosition] == '['))
  78. {
  79. // Have matched a property name. Navigate to value.
  80. ++m_Position;
  81. if (!SkipToValue())
  82. return false;
  83. // Check if we have matched everything in the path.
  84. if (pathPosition >= pathLength)
  85. return true;
  86. if (path[pathPosition] == '/')
  87. {
  88. ++pathPosition;
  89. if (!ParseToken('{'))
  90. return false;
  91. }
  92. else if (path[pathPosition] == '[')
  93. {
  94. ++pathPosition;
  95. if (pathPosition == pathLength)
  96. throw new ArgumentException("Malformed JSON property path: " + path, nameof(path));
  97. if (path[pathPosition] == ']')
  98. {
  99. m_MatchAnyElementInArray = true;
  100. ++pathPosition;
  101. if (pathPosition == pathLength)
  102. return true;
  103. }
  104. else
  105. throw new NotImplementedException("Navigating to specific array element");
  106. }
  107. }
  108. else
  109. {
  110. // This property isn't it. Skip the property and its value and reset
  111. // to where we started in this iteration in the property path.
  112. pathPosition = pathStartPosition;
  113. while (m_Position < m_Length && m_Text[m_Position] != '"')
  114. ++m_Position;
  115. if (m_Position == m_Length || m_Text[m_Position] != '"')
  116. return false;
  117. ++m_Position;
  118. if (!SkipToValue() || !ParseValue())
  119. return false;
  120. SkipWhitespace();
  121. if (m_Position == m_Length || m_Text[m_Position] == '}' || m_Text[m_Position] != ',')
  122. return false;
  123. ++m_Position;
  124. }
  125. }
  126. return false;
  127. }
  128. /// <summary>
  129. /// Return true if the current property has a value matching <paramref name="expectedValue"/>.
  130. /// </summary>
  131. /// <param name="expectedValue"></param>
  132. /// <returns></returns>
  133. public bool CurrentPropertyHasValueEqualTo(JsonValue expectedValue)
  134. {
  135. // Grab property value.
  136. var savedPosition = m_Position;
  137. m_DryRun = false;
  138. if (!ParseValue(out var propertyValue))
  139. {
  140. m_Position = savedPosition;
  141. return false;
  142. }
  143. m_Position = savedPosition;
  144. // Match given value.
  145. var isMatch = false;
  146. if (propertyValue.type == JsonValueType.Array && m_MatchAnyElementInArray)
  147. {
  148. var array = propertyValue.arrayValue;
  149. for (var i = 0; !isMatch && i < array.Count; ++i)
  150. isMatch = array[i] == expectedValue;
  151. }
  152. else
  153. {
  154. isMatch = propertyValue == expectedValue;
  155. }
  156. return isMatch;
  157. }
  158. public bool ParseToken(char token)
  159. {
  160. SkipWhitespace();
  161. if (m_Position == m_Length)
  162. return false;
  163. if (m_Text[m_Position] != token)
  164. return false;
  165. ++m_Position;
  166. SkipWhitespace();
  167. return m_Position < m_Length;
  168. }
  169. public bool ParseValue()
  170. {
  171. return ParseValue(out var result);
  172. }
  173. public bool ParseValue(out JsonValue result)
  174. {
  175. result = default;
  176. SkipWhitespace();
  177. if (m_Position == m_Length)
  178. return false;
  179. var ch = m_Text[m_Position];
  180. switch (ch)
  181. {
  182. case '"':
  183. if (ParseStringValue(out result))
  184. return true;
  185. break;
  186. case '[':
  187. if (ParseArrayValue(out result))
  188. return true;
  189. break;
  190. case '{':
  191. if (ParseObjectValue(out result))
  192. return true;
  193. break;
  194. case 't':
  195. case 'f':
  196. if (ParseBooleanValue(out result))
  197. return true;
  198. break;
  199. case 'n':
  200. if (ParseNullValue(out result))
  201. return true;
  202. break;
  203. default:
  204. if (ParseNumber(out result))
  205. return true;
  206. break;
  207. }
  208. return false;
  209. }
  210. public bool ParseStringValue(out JsonValue result)
  211. {
  212. result = default;
  213. SkipWhitespace();
  214. if (m_Position == m_Length || m_Text[m_Position] != '"')
  215. return false;
  216. ++m_Position;
  217. var startIndex = m_Position;
  218. var hasEscapes = false;
  219. while (m_Position < m_Length)
  220. {
  221. var ch = m_Text[m_Position];
  222. if (ch == '\\')
  223. {
  224. ++m_Position;
  225. if (m_Position == m_Length)
  226. break;
  227. hasEscapes = true;
  228. }
  229. else if (ch == '"')
  230. {
  231. ++m_Position;
  232. result = new JsonString
  233. {
  234. text = new Substring(m_Text, startIndex, m_Position - startIndex - 1),
  235. hasEscapes = hasEscapes
  236. };
  237. return true;
  238. }
  239. ++m_Position;
  240. }
  241. return false;
  242. }
  243. public bool ParseArrayValue(out JsonValue result)
  244. {
  245. result = default;
  246. SkipWhitespace();
  247. if (m_Position == m_Length || m_Text[m_Position] != '[')
  248. return false;
  249. ++m_Position;
  250. if (m_Position == m_Length)
  251. return false;
  252. if (m_Text[m_Position] == ']')
  253. {
  254. // Empty array.
  255. result = new JsonValue { type = JsonValueType.Array };
  256. ++m_Position;
  257. return true;
  258. }
  259. List<JsonValue> values = null;
  260. if (!m_DryRun)
  261. values = new List<JsonValue>();
  262. while (m_Position < m_Length)
  263. {
  264. if (!ParseValue(out var value))
  265. return false;
  266. if (!m_DryRun)
  267. values.Add(value);
  268. SkipWhitespace();
  269. if (m_Position == m_Length)
  270. return false;
  271. var ch = m_Text[m_Position];
  272. if (ch == ']')
  273. {
  274. ++m_Position;
  275. if (!m_DryRun)
  276. result = values;
  277. return true;
  278. }
  279. if (ch == ',')
  280. ++m_Position;
  281. }
  282. return false;
  283. }
  284. public bool ParseObjectValue(out JsonValue result)
  285. {
  286. result = default;
  287. if (!ParseToken('{'))
  288. return false;
  289. if (m_Position < m_Length && m_Text[m_Position] == '}')
  290. {
  291. result = new JsonValue { type = JsonValueType.Object };
  292. ++m_Position;
  293. return true;
  294. }
  295. while (m_Position < m_Length)
  296. {
  297. if (!ParseStringValue(out var propertyName))
  298. return false;
  299. if (!SkipToValue())
  300. return false;
  301. if (!ParseValue(out var propertyValue))
  302. return false;
  303. if (!m_DryRun)
  304. throw new NotImplementedException();
  305. SkipWhitespace();
  306. if (m_Position < m_Length && m_Text[m_Position] == '}')
  307. {
  308. if (!m_DryRun)
  309. throw new NotImplementedException();
  310. ++m_Position;
  311. return true;
  312. }
  313. }
  314. return false;
  315. }
  316. public bool ParseNumber(out JsonValue result)
  317. {
  318. result = default;
  319. SkipWhitespace();
  320. if (m_Position == m_Length)
  321. return false;
  322. var negative = false;
  323. var haveFractionalPart = false;
  324. var integralPart = 0L;
  325. var fractionalPart = 0.0;
  326. var fractionalDivisor = 10.0;
  327. var exponent = 0;
  328. // Parse sign.
  329. if (m_Text[m_Position] == '-')
  330. {
  331. negative = true;
  332. ++m_Position;
  333. }
  334. if (m_Position == m_Length || !char.IsDigit(m_Text[m_Position]))
  335. return false;
  336. // Parse integral part.
  337. while (m_Position < m_Length)
  338. {
  339. var ch = m_Text[m_Position];
  340. if (ch == '.')
  341. break;
  342. if (ch < '0' || ch > '9')
  343. break;
  344. integralPart = integralPart * 10 + ch - '0';
  345. ++m_Position;
  346. }
  347. // Parse fractional part.
  348. if (m_Position < m_Length && m_Text[m_Position] == '.')
  349. {
  350. haveFractionalPart = true;
  351. ++m_Position;
  352. if (m_Position == m_Length || !char.IsDigit(m_Text[m_Position]))
  353. return false;
  354. while (m_Position < m_Length)
  355. {
  356. var ch = m_Text[m_Position];
  357. if (ch < '0' || ch > '9')
  358. break;
  359. fractionalPart = (ch - '0') / fractionalDivisor + fractionalPart;
  360. fractionalDivisor *= 10;
  361. ++m_Position;
  362. }
  363. }
  364. if (m_Position < m_Length && (m_Text[m_Position] == 'e' || m_Text[m_Position] == 'E'))
  365. {
  366. ++m_Position;
  367. var isNegative = false;
  368. if (m_Position < m_Length && m_Text[m_Position] == '-')
  369. {
  370. isNegative = true;
  371. ++m_Position;
  372. }
  373. else if (m_Position < m_Length && m_Text[m_Position] == '+')
  374. {
  375. ++m_Position;
  376. }
  377. var multiplier = 1;
  378. while (m_Position < m_Length && char.IsDigit(m_Text[m_Position]))
  379. {
  380. var digit = m_Text[m_Position] - '0';
  381. exponent *= multiplier;
  382. exponent += digit;
  383. multiplier *= 10;
  384. ++m_Position;
  385. }
  386. if (isNegative)
  387. exponent *= -1;
  388. }
  389. if (!m_DryRun)
  390. {
  391. if (!haveFractionalPart && exponent == 0)
  392. {
  393. if (negative)
  394. result = -integralPart;
  395. else
  396. result = integralPart;
  397. }
  398. else
  399. {
  400. float value;
  401. if (negative)
  402. value = (float)-(integralPart + fractionalPart);
  403. else
  404. value = (float)(integralPart + fractionalPart);
  405. if (exponent != 0)
  406. value *= Mathf.Pow(10, exponent);
  407. result = value;
  408. }
  409. }
  410. return true;
  411. }
  412. public bool ParseBooleanValue(out JsonValue result)
  413. {
  414. SkipWhitespace();
  415. if (SkipString("true"))
  416. {
  417. result = true;
  418. return true;
  419. }
  420. if (SkipString("false"))
  421. {
  422. result = false;
  423. return true;
  424. }
  425. result = default;
  426. return false;
  427. }
  428. public bool ParseNullValue(out JsonValue result)
  429. {
  430. result = default;
  431. return SkipString("null");
  432. }
  433. public bool SkipToValue()
  434. {
  435. SkipWhitespace();
  436. if (m_Position == m_Length || m_Text[m_Position] != ':')
  437. return false;
  438. ++m_Position;
  439. SkipWhitespace();
  440. return true;
  441. }
  442. private bool SkipString(string text)
  443. {
  444. SkipWhitespace();
  445. var length = text.Length;
  446. if (m_Position + length >= m_Length)
  447. return false;
  448. for (var i = 0; i < length; ++i)
  449. {
  450. if (m_Text[m_Position + i] != text[i])
  451. return false;
  452. }
  453. m_Position += length;
  454. return true;
  455. }
  456. private void SkipWhitespace()
  457. {
  458. while (m_Position < m_Length && char.IsWhiteSpace(m_Text[m_Position]))
  459. ++m_Position;
  460. }
  461. public bool isAtEnd => m_Position >= m_Length;
  462. private readonly string m_Text;
  463. private readonly int m_Length;
  464. private int m_Position;
  465. private bool m_MatchAnyElementInArray;
  466. private bool m_DryRun;
  467. public enum JsonValueType
  468. {
  469. None,
  470. Bool,
  471. Real,
  472. Integer,
  473. String,
  474. Array,
  475. Object,
  476. Any,
  477. }
  478. public struct JsonString : IEquatable<JsonString>
  479. {
  480. public Substring text;
  481. public bool hasEscapes;
  482. public override string ToString()
  483. {
  484. if (!hasEscapes)
  485. return text.ToString();
  486. var builder = new StringBuilder();
  487. var length = text.length;
  488. for (var i = 0; i < length; ++i)
  489. {
  490. var ch = text[i];
  491. if (ch == '\\')
  492. {
  493. ++i;
  494. if (i == length)
  495. break;
  496. ch = text[i];
  497. }
  498. builder.Append(ch);
  499. }
  500. return builder.ToString();
  501. }
  502. public bool Equals(JsonString other)
  503. {
  504. if (hasEscapes == other.hasEscapes)
  505. return Substring.Compare(text, other.text, StringComparison.InvariantCultureIgnoreCase) == 0;
  506. var thisLength = text.length;
  507. var otherLength = other.text.length;
  508. int thisIndex = 0, otherIndex = 0;
  509. for (; thisIndex < thisLength && otherIndex < otherLength; ++thisIndex, ++otherIndex)
  510. {
  511. var thisChar = text[thisIndex];
  512. var otherChar = other.text[otherIndex];
  513. if (thisChar == '\\')
  514. {
  515. ++thisIndex;
  516. if (thisIndex == thisLength)
  517. return false;
  518. thisChar = text[thisIndex];
  519. }
  520. if (otherChar == '\\')
  521. {
  522. ++otherIndex;
  523. if (otherIndex == otherLength)
  524. return false;
  525. otherChar = other.text[otherIndex];
  526. }
  527. if (char.ToUpperInvariant(thisChar) != char.ToUpperInvariant(otherChar))
  528. return false;
  529. }
  530. return thisIndex == thisLength && otherIndex == otherLength;
  531. }
  532. public override bool Equals(object obj)
  533. {
  534. return obj is JsonString other && Equals(other);
  535. }
  536. public override int GetHashCode()
  537. {
  538. unchecked
  539. {
  540. return (text.GetHashCode() * 397) ^ hasEscapes.GetHashCode();
  541. }
  542. }
  543. public static bool operator==(JsonString left, JsonString right)
  544. {
  545. return left.Equals(right);
  546. }
  547. public static bool operator!=(JsonString left, JsonString right)
  548. {
  549. return !left.Equals(right);
  550. }
  551. public static implicit operator JsonString(string str)
  552. {
  553. return new JsonString { text = str };
  554. }
  555. }
  556. public struct JsonValue : IEquatable<JsonValue>
  557. {
  558. public JsonValueType type;
  559. public bool boolValue;
  560. public double realValue;
  561. public long integerValue;
  562. public JsonString stringValue;
  563. public List<JsonValue> arrayValue; // Allocates.
  564. public Dictionary<string, JsonValue> objectValue; // Allocates.
  565. public object anyValue;
  566. public bool ToBoolean()
  567. {
  568. switch (type)
  569. {
  570. case JsonValueType.Bool: return boolValue;
  571. case JsonValueType.Integer: return integerValue != 0;
  572. case JsonValueType.Real: return NumberHelpers.Approximately(0, realValue);
  573. case JsonValueType.String: return Convert.ToBoolean(ToString());
  574. }
  575. return default;
  576. }
  577. public long ToInteger()
  578. {
  579. switch (type)
  580. {
  581. case JsonValueType.Bool: return boolValue ? 1 : 0;
  582. case JsonValueType.Integer: return integerValue;
  583. case JsonValueType.Real: return (long)realValue;
  584. case JsonValueType.String: return Convert.ToInt64(ToString());
  585. }
  586. return default;
  587. }
  588. public double ToDouble()
  589. {
  590. switch (type)
  591. {
  592. case JsonValueType.Bool: return boolValue ? 1 : 0;
  593. case JsonValueType.Integer: return integerValue;
  594. case JsonValueType.Real: return realValue;
  595. case JsonValueType.String: return Convert.ToSingle(ToString());
  596. }
  597. return default;
  598. }
  599. public override string ToString()
  600. {
  601. switch (type)
  602. {
  603. case JsonValueType.None: return "null";
  604. case JsonValueType.Bool: return boolValue.ToString();
  605. case JsonValueType.Integer: return integerValue.ToString(CultureInfo.InvariantCulture);
  606. case JsonValueType.Real: return realValue.ToString(CultureInfo.InvariantCulture);
  607. case JsonValueType.String: return stringValue.ToString();
  608. case JsonValueType.Array:
  609. if (arrayValue == null)
  610. return "[]";
  611. return $"[{string.Join(",", arrayValue.Select(x => x.ToString()))}]";
  612. case JsonValueType.Object:
  613. if (objectValue == null)
  614. return "{}";
  615. var elements = objectValue.Select(pair => $"\"{pair.Key}\" : \"{pair.Value}\"");
  616. return $"{{{string.Join(",", elements)}}}";
  617. case JsonValueType.Any: return anyValue.ToString();
  618. }
  619. return base.ToString();
  620. }
  621. public static implicit operator JsonValue(bool val)
  622. {
  623. return new JsonValue
  624. {
  625. type = JsonValueType.Bool,
  626. boolValue = val
  627. };
  628. }
  629. public static implicit operator JsonValue(long val)
  630. {
  631. return new JsonValue
  632. {
  633. type = JsonValueType.Integer,
  634. integerValue = val
  635. };
  636. }
  637. public static implicit operator JsonValue(double val)
  638. {
  639. return new JsonValue
  640. {
  641. type = JsonValueType.Real,
  642. realValue = val
  643. };
  644. }
  645. public static implicit operator JsonValue(string str)
  646. {
  647. return new JsonValue
  648. {
  649. type = JsonValueType.String,
  650. stringValue = new JsonString { text = str }
  651. };
  652. }
  653. public static implicit operator JsonValue(JsonString str)
  654. {
  655. return new JsonValue
  656. {
  657. type = JsonValueType.String,
  658. stringValue = str
  659. };
  660. }
  661. public static implicit operator JsonValue(List<JsonValue> array)
  662. {
  663. return new JsonValue
  664. {
  665. type = JsonValueType.Array,
  666. arrayValue = array
  667. };
  668. }
  669. public static implicit operator JsonValue(Dictionary<string, JsonValue> obj)
  670. {
  671. return new JsonValue
  672. {
  673. type = JsonValueType.Object,
  674. objectValue = obj
  675. };
  676. }
  677. public static implicit operator JsonValue(Enum val)
  678. {
  679. return new JsonValue
  680. {
  681. type = JsonValueType.Any,
  682. anyValue = val
  683. };
  684. }
  685. public bool Equals(JsonValue other)
  686. {
  687. // Default comparisons.
  688. if (type == other.type)
  689. {
  690. switch (type)
  691. {
  692. case JsonValueType.None: return true;
  693. case JsonValueType.Bool: return boolValue == other.boolValue;
  694. case JsonValueType.Integer: return integerValue == other.integerValue;
  695. case JsonValueType.Real: return NumberHelpers.Approximately(realValue, other.realValue);
  696. case JsonValueType.String: return stringValue == other.stringValue;
  697. case JsonValueType.Object: throw new NotImplementedException();
  698. case JsonValueType.Array: throw new NotImplementedException();
  699. case JsonValueType.Any: return anyValue.Equals(other.anyValue);
  700. }
  701. return false;
  702. }
  703. // anyValue-based comparisons.
  704. if (anyValue != null)
  705. return Equals(anyValue, other);
  706. if (other.anyValue != null)
  707. return Equals(other.anyValue, this);
  708. return false;
  709. }
  710. private static bool Equals(object obj, JsonValue value)
  711. {
  712. if (obj == null)
  713. return false;
  714. if (obj is Regex regex)
  715. return regex.IsMatch(value.ToString());
  716. if (obj is string str)
  717. {
  718. switch (value.type)
  719. {
  720. case JsonValueType.String: return value.stringValue == str;
  721. case JsonValueType.Integer: return long.TryParse(str, out var si) && si == value.integerValue;
  722. case JsonValueType.Real:
  723. return double.TryParse(str, out var sf) && NumberHelpers.Approximately(sf, value.realValue);
  724. case JsonValueType.Bool:
  725. if (value.boolValue)
  726. return str == "True" || str == "true" || str == "1";
  727. return str == "False" || str == "false" || str == "0";
  728. }
  729. }
  730. if (obj is float f)
  731. {
  732. if (value.type == JsonValueType.Real)
  733. return NumberHelpers.Approximately(f, value.realValue);
  734. if (value.type == JsonValueType.String)
  735. return float.TryParse(value.ToString(), out var otherF) && Mathf.Approximately(f, otherF);
  736. }
  737. if (obj is double d)
  738. {
  739. if (value.type == JsonValueType.Real)
  740. return NumberHelpers.Approximately(d, value.realValue);
  741. if (value.type == JsonValueType.String)
  742. return double.TryParse(value.ToString(), out var otherD) &&
  743. NumberHelpers.Approximately(d, otherD);
  744. }
  745. if (obj is int i)
  746. {
  747. if (value.type == JsonValueType.Integer)
  748. return i == value.integerValue;
  749. if (value.type == JsonValueType.String)
  750. return int.TryParse(value.ToString(), out var otherI) && i == otherI;
  751. }
  752. if (obj is long l)
  753. {
  754. if (value.type == JsonValueType.Integer)
  755. return l == value.integerValue;
  756. if (value.type == JsonValueType.String)
  757. return long.TryParse(value.ToString(), out var otherL) && l == otherL;
  758. }
  759. if (obj is bool b)
  760. {
  761. if (value.type == JsonValueType.Bool)
  762. return b == value.boolValue;
  763. if (value.type == JsonValueType.String)
  764. {
  765. if (b)
  766. return value.stringValue == "true" || value.stringValue == "True" ||
  767. value.stringValue == "1";
  768. return value.stringValue == "false" || value.stringValue == "False" ||
  769. value.stringValue == "0";
  770. }
  771. }
  772. // NOTE: The enum-based comparisons allocate both on the Convert.ToInt64() and Enum.GetName() path. I've found
  773. // no way to do either comparison in a way that does not allocate.
  774. if (obj is Enum)
  775. {
  776. if (value.type == JsonValueType.Integer)
  777. return Convert.ToInt64(obj) == value.integerValue;
  778. if (value.type == JsonValueType.String)
  779. return value.stringValue == Enum.GetName(obj.GetType(), obj);
  780. }
  781. return false;
  782. }
  783. public override bool Equals(object obj)
  784. {
  785. return obj is JsonValue other && Equals(other);
  786. }
  787. public override int GetHashCode()
  788. {
  789. unchecked
  790. {
  791. var hashCode = (int)type;
  792. hashCode = (hashCode * 397) ^ boolValue.GetHashCode();
  793. hashCode = (hashCode * 397) ^ realValue.GetHashCode();
  794. hashCode = (hashCode * 397) ^ integerValue.GetHashCode();
  795. hashCode = (hashCode * 397) ^ stringValue.GetHashCode();
  796. hashCode = (hashCode * 397) ^ (arrayValue != null ? arrayValue.GetHashCode() : 0);
  797. hashCode = (hashCode * 397) ^ (objectValue != null ? objectValue.GetHashCode() : 0);
  798. hashCode = (hashCode * 397) ^ (anyValue != null ? anyValue.GetHashCode() : 0);
  799. return hashCode;
  800. }
  801. }
  802. public static bool operator==(JsonValue left, JsonValue right)
  803. {
  804. return left.Equals(right);
  805. }
  806. public static bool operator!=(JsonValue left, JsonValue right)
  807. {
  808. return !left.Equals(right);
  809. }
  810. }
  811. }
  812. }