/src/Raven.Server/Json/BlittableJsonTraverser.cs

https://github.com/fitzchak/ravendb · C# · 302 lines · 267 code · 35 blank · 0 comment · 39 complexity · bfebae56952f403323a240993662853f MD5 · raw file

  1. using System;
  2. using System.Collections;
  3. using System.Collections.Generic;
  4. using Sparrow;
  5. using Sparrow.Json;
  6. namespace Raven.Server.Json
  7. {
  8. public class BlittableJsonTraverser
  9. {
  10. public static BlittableJsonTraverser Default = new BlittableJsonTraverser();
  11. public static BlittableJsonTraverser FlatMapReduceResults = new BlittableJsonTraverser(new char[] { }); // map-reduce results have always a flat structure, let's ignore separators
  12. private const char PropertySeparator = '.';
  13. private const char CollectionSeparatorStartBracket = '[';
  14. private const char CollectionSeparatorEndBracket = ']';
  15. public static readonly char[] CollectionAndPropertySeparators = { CollectionSeparatorStartBracket, CollectionSeparatorEndBracket, PropertySeparator };
  16. public static readonly char[] PropertySeparators =
  17. {
  18. PropertySeparator
  19. };
  20. private readonly char[] _separators =
  21. {
  22. PropertySeparator,
  23. CollectionSeparatorStartBracket
  24. };
  25. private BlittableJsonTraverser(char[] nonDefaultSeparators = null)
  26. {
  27. if (nonDefaultSeparators != null)
  28. _separators = nonDefaultSeparators;
  29. }
  30. public object Read(BlittableJsonReaderObject docReader, StringSegment path)
  31. {
  32. if (TryRead(docReader, path, out var result, out _) == false)
  33. throw new InvalidOperationException($"Invalid path: {path}.");
  34. return result;
  35. }
  36. public bool TryRead(BlittableJsonReaderObject docReader, StringSegment path, out object result, out StringSegment leftPath)
  37. {
  38. var propertySegment = GetNextToken(path, out int propertySegmentLength);
  39. if (docReader.TryGetMember(propertySegment, out var reader) == false)
  40. {
  41. leftPath = path;
  42. result = null;
  43. return false;
  44. }
  45. if (propertySegmentLength == path.Length || propertySegmentLength == -1)
  46. {
  47. leftPath = string.Empty;// we read it all
  48. result = reader;
  49. return true;
  50. }
  51. switch (path[propertySegmentLength])
  52. {
  53. case PropertySeparator:
  54. var pathSegment = path.Subsegment(propertySegmentLength + 1);
  55. if (reader is BlittableJsonReaderObject propertyInnerObject)
  56. {
  57. if (TryRead(propertyInnerObject, pathSegment, out result, out leftPath))
  58. return true;
  59. if (result == null)
  60. result = reader;
  61. return false;
  62. }
  63. leftPath = pathSegment;
  64. result = reader;
  65. return false;
  66. case CollectionSeparatorStartBracket:
  67. var type = GetCollectionSeparatorType(path, propertySegmentLength);
  68. switch (type)
  69. {
  70. case CollectionSeparatorType.BracketsOnly:
  71. leftPath = string.Empty; // we are done with this path
  72. if (reader is BlittableJsonReaderArray innerArray)
  73. {
  74. result = ReadArray(innerArray, leftPath);
  75. return true;
  76. }
  77. result = null;
  78. leftPath = path;
  79. return false;
  80. case CollectionSeparatorType.MultiBrackets:
  81. leftPath = path.Subsegment(propertySegmentLength + 2);
  82. if (reader is BlittableJsonReaderArray multiDimensionalArray)
  83. {
  84. result = ReadArray(multiDimensionalArray, leftPath);
  85. leftPath = string.Empty; // we consume and handle internally the rest of it
  86. return true;
  87. }
  88. result = reader;
  89. return false;
  90. case CollectionSeparatorType.BracketsAndProperty:
  91. leftPath = path.Subsegment(propertySegmentLength + CollectionAndPropertySeparators.Length);
  92. if (reader is BlittableJsonReaderArray collectionInnerArray)
  93. {
  94. result = ReadArray(collectionInnerArray, leftPath);
  95. leftPath = string.Empty; // we consume and handle internally the rest of it
  96. return true;
  97. }
  98. if (reader is BlittableJsonReaderObject nested)
  99. {
  100. return ReadNestedObjects(nested, leftPath, out result, out leftPath);
  101. }
  102. result = reader;
  103. return false;
  104. default:
  105. throw new NotSupportedException($"Invalid collection separator in a given path: {path}");
  106. }
  107. default:
  108. throw new NotSupportedException($"Unhandled separator character: {path[propertySegmentLength]}");
  109. }
  110. }
  111. private enum CollectionSeparatorType
  112. {
  113. BracketsAndProperty,
  114. BracketsOnly,
  115. MultiBrackets,
  116. Invalid
  117. }
  118. private CollectionSeparatorType GetCollectionSeparatorType(StringSegment path, int propertySegmentLength)
  119. {
  120. if (path.Length < propertySegmentLength + CollectionAndPropertySeparators.Length)
  121. {
  122. if (path.Length <= propertySegmentLength + 1)
  123. return CollectionSeparatorType.Invalid;
  124. if (path[propertySegmentLength + 1] != CollectionSeparatorEndBracket)
  125. return CollectionSeparatorType.Invalid;
  126. return CollectionSeparatorType.BracketsOnly;
  127. }
  128. if (path[propertySegmentLength + 1] != CollectionSeparatorEndBracket)
  129. return CollectionSeparatorType.Invalid;
  130. if (path[propertySegmentLength + 2] != '.')
  131. {
  132. if (path[propertySegmentLength + 2] == CollectionSeparatorStartBracket)
  133. return CollectionSeparatorType.MultiBrackets;
  134. return CollectionSeparatorType.Invalid;
  135. }
  136. return CollectionSeparatorType.BracketsAndProperty;
  137. }
  138. private StringSegment GetNextToken(StringSegment path, out int consumed)
  139. {
  140. int SkipQoute(char ch, int i)
  141. {
  142. for (int j = i; j < path.Length; j++)
  143. {
  144. if (path[j] == '\'')
  145. {
  146. j++;// escape next chart
  147. continue;
  148. }
  149. if (path[j] == ch)
  150. return j;
  151. }
  152. return path.Length;
  153. }
  154. if (path.Length == 0)
  155. {
  156. consumed = 0;
  157. return path;
  158. }
  159. if (path[0] == '"' || path[0] == '\'')
  160. {
  161. consumed = SkipQoute(path[0], 1);
  162. return path.Subsegment(1, consumed - 2);
  163. }
  164. consumed = path.IndexOfAny(_separators, 0);
  165. return path.Subsegment(0, consumed);
  166. }
  167. private bool ReadNestedObjects(BlittableJsonReaderObject nested, StringSegment path, out object result, out StringSegment leftPath)
  168. {
  169. var propCount = nested.Count;
  170. var prop = new BlittableJsonReaderObject.PropertyDetails();
  171. List<object> results = null;
  172. leftPath = path; // note that we assume identical sturcutre of all values in this document
  173. void AddItemToResults(object i)
  174. {
  175. if (results == null)
  176. results = new List<object>();
  177. results.Add(i);
  178. }
  179. for (int i = 0; i < propCount; i++)
  180. {
  181. nested.GetPropertyByIndex(i, ref prop);
  182. if (prop.Value is BlittableJsonReaderObject nestedVal)
  183. {
  184. if (TryRead(nestedVal, path, out var item, out leftPath))
  185. {
  186. AddItemToResults(item);
  187. }
  188. else
  189. {
  190. item = item ?? nested;
  191. if (BlittableJsonTraverserHelper.TryReadComputedProperties(this, leftPath, ref item))
  192. {
  193. leftPath = string.Empty;// consumed entirely
  194. AddItemToResults(item);
  195. }
  196. }
  197. }
  198. }
  199. result = results ?? (object)nested;
  200. return results != null;
  201. }
  202. private IEnumerable<object> ReadArray(BlittableJsonReaderArray array, StringSegment pathSegment)
  203. {
  204. // ReSharper disable once ForCanBeConvertedToForeach
  205. for (int i = 0; i < array.Length; i++)
  206. {
  207. var item = array[i];
  208. if (item is BlittableJsonReaderObject arrayObject)
  209. {
  210. if (BlittableJsonTraverserHelper.TryRead(this, arrayObject, pathSegment, out var result))
  211. {
  212. if (result is IEnumerable enumerable && result is string == false)
  213. {
  214. foreach (var nestedItem in enumerable)
  215. {
  216. yield return nestedItem;
  217. }
  218. }
  219. else
  220. {
  221. yield return result;
  222. }
  223. }
  224. else
  225. {
  226. yield return null;
  227. }
  228. }
  229. else
  230. {
  231. if (item is BlittableJsonReaderArray arrayReader)
  232. {
  233. var type = GetCollectionSeparatorType(pathSegment, 0);
  234. StringSegment subSegment;
  235. switch (type)
  236. {
  237. case CollectionSeparatorType.BracketsAndProperty:
  238. subSegment = pathSegment.Subsegment(CollectionAndPropertySeparators.Length);
  239. break;
  240. case CollectionSeparatorType.MultiBrackets:
  241. subSegment = pathSegment.Subsegment(2);
  242. break;
  243. case CollectionSeparatorType.BracketsOnly:
  244. subSegment = string.Empty;
  245. break;
  246. default:
  247. throw new NotSupportedException($"Invalid collection separator in a given path: {pathSegment}");
  248. }
  249. foreach (var nestedItem in ReadArray(arrayReader, subSegment))
  250. {
  251. yield return nestedItem;
  252. }
  253. }
  254. else
  255. {
  256. yield return item;
  257. }
  258. }
  259. }
  260. }
  261. }
  262. }