Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
237 changes: 201 additions & 36 deletions README.md

Large diffs are not rendered by default.

9 changes: 9 additions & 0 deletions src/Hyperbee.Json/Descriptors/ChildEnumerationOptions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace Hyperbee.Json.Descriptors;

[Flags]
public enum ChildEnumerationOptions
{
None = 0,
ComplexTypesOnly = 1,
Reverse = 2,
}
73 changes: 64 additions & 9 deletions src/Hyperbee.Json/Descriptors/Element/ElementActions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,18 +33,72 @@ public bool TryGetFromPointer( in JsonElement node, JsonSegment segment, out Jso
public bool DeepEquals( JsonElement left, JsonElement right ) =>
left.DeepEquals( right );

public IEnumerable<(JsonElement Value, string Key)> GetChildren( in JsonElement value, bool complexTypesOnly = false )
public IEnumerable<JsonElement> GetChildren( JsonElement value, ChildEnumerationOptions options )
{
bool complexTypesOnly = options.HasFlag( ChildEnumerationOptions.ComplexTypesOnly );
bool reverse = options.HasFlag( ChildEnumerationOptions.Reverse );

// allocating is faster than using yield return and less memory intensive.
// using a collection results in fewer overall allocations than calling
// LINQ reverse, which internally allocates, and then discards, a new array.

List<JsonElement> results;

switch ( value.ValueKind )
{
case JsonValueKind.Array:
{
var length = value.GetArrayLength();
results = new List<JsonElement>( length );

for ( var index = 0; index < length; index++ )
{
var child = value[index];

if ( complexTypesOnly && child.ValueKind is not (JsonValueKind.Array or JsonValueKind.Object) )
continue;

results.Add( child );
}

return reverse ? results.EnumerateReverse() : results;
}
case JsonValueKind.Object:
{
results = new List<JsonElement>( 8 );

foreach ( var child in value.EnumerateObject() )
{
if ( complexTypesOnly && child.Value.ValueKind is not (JsonValueKind.Array or JsonValueKind.Object) )
continue;

results.Add( child.Value );
}

return reverse ? results.EnumerateReverse() : results;
}
}

return [];
}

public IEnumerable<(JsonElement Value, string Key)> GetChildrenWithName( in JsonElement value, ChildEnumerationOptions options )
{
bool complexTypesOnly = options.HasFlag( ChildEnumerationOptions.ComplexTypesOnly );
bool reverse = options.HasFlag( ChildEnumerationOptions.Reverse );

// allocating is faster than using yield return and less memory intensive.
// using stack results in fewer overall allocations than calling reverse,
// which internally allocates, and then discards, a new array.
// using a collection results in fewer overall allocations than calling
// LINQ reverse, which internally allocates, and then discards, a new array.

List<(JsonElement, string)> results;

switch ( value.ValueKind )
{
case JsonValueKind.Array:
{
var length = value.GetArrayLength();
var results = new Stack<(JsonElement, string)>( length ); // stack will reverse items
results = new List<(JsonElement, string)>( length );

for ( var index = 0; index < length; index++ )
{
Expand All @@ -53,27 +107,28 @@ public bool DeepEquals( JsonElement left, JsonElement right ) =>
if ( complexTypesOnly && child.ValueKind is not (JsonValueKind.Array or JsonValueKind.Object) )
continue;

results.Push( (child, IndexHelper.GetIndexString( index )) );
results.Add( (child, IndexHelper.GetIndexString( index )) );
}

return results;
return reverse ? results.EnumerateReverse() : results;
}
case JsonValueKind.Object:
{
var results = new Stack<(JsonElement, string)>(); // stack will reverse items
results = new List<(JsonElement, string)>( 8 );

foreach ( var child in value.EnumerateObject() )
{
if ( complexTypesOnly && child.Value.ValueKind is not (JsonValueKind.Array or JsonValueKind.Object) )
continue;

results.Push( (child.Value, child.Name) );
results.Add( (child.Value, child.Name) );
}

return results;
return reverse ? results.EnumerateReverse() : results;
}
}

return [];
}
}

3 changes: 2 additions & 1 deletion src/Hyperbee.Json/Descriptors/INodeActions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,6 @@ public interface INodeActions<TNode>

public bool DeepEquals( TNode left, TNode right );

public IEnumerable<(TNode Value, string Key)> GetChildren( in TNode value, bool complexTypesOnly = false );
public IEnumerable<(TNode Value, string Key)> GetChildrenWithName( in TNode value, ChildEnumerationOptions options );
public IEnumerable<TNode> GetChildren( TNode value, ChildEnumerationOptions options );
}
73 changes: 64 additions & 9 deletions src/Hyperbee.Json/Descriptors/Node/NodeActions.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System.Text.Json;
using System.Text.Json.Nodes;
using Hyperbee.Json.Extensions;
using Hyperbee.Json.Path;
using Hyperbee.Json.Pointer;
using Hyperbee.Json.Query;
Expand Down Expand Up @@ -28,18 +29,72 @@ public bool TryGetFromPointer( in JsonNode node, JsonSegment segment, out JsonNo
public bool DeepEquals( JsonNode left, JsonNode right ) =>
JsonNode.DeepEquals( left, right );

public IEnumerable<(JsonNode Value, string Key)> GetChildren( in JsonNode value, bool complexTypesOnly = false )
public IEnumerable<JsonNode> GetChildren( JsonNode value, ChildEnumerationOptions options )
{
bool complexTypesOnly = options.HasFlag( ChildEnumerationOptions.ComplexTypesOnly );
bool reverse = options.HasFlag( ChildEnumerationOptions.Reverse );

// allocating is faster than using yield return and less memory intensive.
// using stack results in fewer overall allocations than calling reverse,
// which internally allocates, and then discards, a new array.
// using a collection results in fewer overall allocations than calling
// LINQ reverse, which internally allocates, and then discards, a new array.

List<JsonNode> results;

switch ( value )
{
case JsonArray jsonArray:
{
var length = jsonArray.Count;
results = new List<JsonNode>( length );

for ( var index = 0; index < length; index++ )
{
var child = value[index];

if ( complexTypesOnly && child is not (JsonArray or JsonObject) )
continue;

results.Add( child );
}

return reverse ? results.EnumerateReverse() : results;
}
case JsonObject jsonObject:
{
results = new List<JsonNode>( 8 );

foreach ( var child in jsonObject )
{
if ( complexTypesOnly && child.Value is not (JsonArray or JsonObject) )
continue;

results.Add( child.Value );
}

return reverse ? results.EnumerateReverse() : results;
}
}

return [];
}

public IEnumerable<(JsonNode Value, string Key)> GetChildrenWithName( in JsonNode value, ChildEnumerationOptions options )
{
bool complexTypesOnly = options.HasFlag( ChildEnumerationOptions.ComplexTypesOnly );
bool reverse = options.HasFlag( ChildEnumerationOptions.Reverse );

// allocating is faster than using yield return and less memory intensive.
// using a collection results in fewer overall allocations than calling
// LINQ reverse, which internally allocates, and then discards, a new array.

List<(JsonNode, string)> results;

switch ( value )
{
case JsonArray jsonArray:
{
var length = jsonArray.Count;
var results = new Stack<(JsonNode, string)>( length ); // stack will reverse items
results = new List<(JsonNode, string)>( length );

for ( var index = 0; index < length; index++ )
{
Expand All @@ -48,24 +103,24 @@ public bool DeepEquals( JsonNode left, JsonNode right ) =>
if ( complexTypesOnly && child is not (JsonArray or JsonObject) )
continue;

results.Push( (child, IndexHelper.GetIndexString( index )) );
results.Add( (child, IndexHelper.GetIndexString( index )) );
}

return results;
return reverse ? results.EnumerateReverse() : results;
}
case JsonObject jsonObject:
{
var results = new Stack<(JsonNode, string)>(); // stack will reverse items
results = new List<(JsonNode, string)>( 8 );

foreach ( var child in jsonObject )
{
if ( complexTypesOnly && child.Value is not (JsonArray or JsonObject) )
continue;

results.Push( (child.Value, child.Key) );
results.Add( (child.Value, child.Key) );
}

return results;
return reverse ? results.EnumerateReverse() : results;
}
}

Expand Down
10 changes: 10 additions & 0 deletions src/Hyperbee.Json/Extensions/ListExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
namespace Hyperbee.Json.Extensions;

public static class ListExtensions
{
internal static IEnumerable<T> EnumerateReverse<T>( this IList<T> list )
{
for ( var i = list.Count - 1; i >= 0; i-- )
yield return list[i];
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ private static bool TryParseNode<TNode>( INodeActions<TNode> actions, ReadOnlySp
private static void ConvertToDoubleQuotes( ref Span<byte> buffer, int length )
{
var insideString = false;

for ( var i = 0; i < length; i++ )
{
if ( buffer[i] == (byte) '\"' )
Expand Down
Loading
Loading