Skip to content

Commit

Permalink
Adds JsonElement.ValueEquals / JsonProperty.NameEquals APIs, (dotnet/…
Browse files Browse the repository at this point in the history
…corefx#37911)

* - Rename Utf8JsonReader.TextEquals to Utf8JsonReader.ValueTextEquals
- Implement JsonElement.ValueEquals and add code coverage
- Reuse logic for JsonProperty and add code coverage
- improve header documentation
- cleanup tests

Commit migrated from dotnet/corefx@f09135a
  • Loading branch information
maryamariyan authored Jun 4, 2019
1 parent 5fb8b93 commit ab1d04e
Show file tree
Hide file tree
Showing 10 changed files with 628 additions and 54 deletions.
11 changes: 9 additions & 2 deletions src/libraries/System.Text.Json/ref/System.Text.Json.cs
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,9 @@ public readonly partial struct JsonElement
public bool TryGetUInt32(out uint value) { throw null; }
[System.CLSCompliantAttribute(false)]
public bool TryGetUInt64(out ulong value) { throw null; }
public bool ValueEquals(System.ReadOnlySpan<byte> utf8Text) { throw null; }
public bool ValueEquals(System.ReadOnlySpan<char> text) { throw null; }
public bool ValueEquals(string text) { throw null; }
public void WriteAsProperty(System.ReadOnlySpan<byte> utf8PropertyName, System.Text.Json.Utf8JsonWriter writer) { }
public void WriteAsProperty(System.ReadOnlySpan<char> propertyName, System.Text.Json.Utf8JsonWriter writer) { }
public void WriteAsProperty(string propertyName, System.Text.Json.Utf8JsonWriter writer) { }
Expand Down Expand Up @@ -131,6 +134,9 @@ public readonly partial struct JsonProperty
private readonly object _dummy;
public string Name { get { throw null; } }
public System.Text.Json.JsonElement Value { get { throw null; } }
public bool NameEquals(System.ReadOnlySpan<byte> utf8Text) { throw null; }
public bool NameEquals(System.ReadOnlySpan<char> text) { throw null; }
public bool NameEquals(string text) { throw null; }
public override string ToString() { throw null; }
}
public partial struct JsonReaderOptions
Expand Down Expand Up @@ -215,8 +221,6 @@ public ref partial struct Utf8JsonReader
public ulong GetUInt64() { throw null; }
public bool Read() { throw null; }
public void Skip() { }
public bool TextEquals(System.ReadOnlySpan<byte> otherUtf8Text) { throw null; }
public bool TextEquals(System.ReadOnlySpan<char> otherText) { throw null; }
public bool TryGetBytesFromBase64(out byte[] value) { throw null; }
public bool TryGetDateTime(out System.DateTime value) { throw null; }
public bool TryGetDateTimeOffset(out System.DateTimeOffset value) { throw null; }
Expand All @@ -231,6 +235,9 @@ public void Skip() { }
[System.CLSCompliantAttribute(false)]
public bool TryGetUInt64(out ulong value) { throw null; }
public bool TrySkip() { throw null; }
public bool ValueTextEquals(System.ReadOnlySpan<byte> utf8Text) { throw null; }
public bool ValueTextEquals(System.ReadOnlySpan<char> text) { throw null; }
public bool ValueTextEquals(string text) { throw null; }
}
public sealed partial class Utf8JsonWriter : System.IDisposable
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System;
using System.Buffers;
using System.Buffers.Text;
using System.Diagnostics;
Expand Down Expand Up @@ -257,6 +258,88 @@ internal string GetString(int index, JsonTokenType expectedType)
return lastString;
}

internal bool TextEquals(int index, ReadOnlySpan<char> otherText, bool isPropertyName)
{
CheckNotDisposed();

int matchIndex = isPropertyName ? index - DbRow.Size : index;

(int lastIdx, string lastString) = _lastIndexAndString;

if (lastIdx == matchIndex)
{
return otherText.SequenceEqual(lastString.AsSpan());
}

byte[] otherUtf8TextArray = null;

int length = checked(otherText.Length * JsonConstants.MaxExpansionFactorWhileTranscoding);
Span<byte> otherUtf8Text = length <= JsonConstants.StackallocThreshold ?
stackalloc byte[length] :
(otherUtf8TextArray = ArrayPool<byte>.Shared.Rent(length));

ReadOnlySpan<byte> utf16Text = MemoryMarshal.AsBytes(otherText);
OperationStatus status = JsonWriterHelper.ToUtf8(utf16Text, otherUtf8Text, out int consumed, out int written);
Debug.Assert(status != OperationStatus.DestinationTooSmall);
if (status > OperationStatus.DestinationTooSmall) // Equivalent to: (status == NeedMoreData || status == InvalidData)
{
return false;
}
Debug.Assert(status == OperationStatus.Done);
Debug.Assert(consumed == utf16Text.Length);

bool result = TextEquals(index, otherUtf8Text.Slice(0, written), isPropertyName);

if (otherUtf8TextArray != null)
{
otherUtf8Text.Slice(0, written).Clear();
ArrayPool<byte>.Shared.Return(otherUtf8TextArray);
}

return result;
}

internal bool TextEquals(int index, ReadOnlySpan<byte> otherUtf8Text, bool isPropertyName)
{
CheckNotDisposed();

int matchIndex = isPropertyName ? index - DbRow.Size : index;

DbRow row = _parsedData.Get(matchIndex);

CheckExpectedType(
isPropertyName? JsonTokenType.PropertyName : JsonTokenType.String,
row.TokenType);

ReadOnlySpan<byte> data = _utf8Json.Span;
ReadOnlySpan<byte> segment = data.Slice(row.Location, row.SizeOrLength);

if (otherUtf8Text.Length > segment.Length)
{
return false;
}

if (row.HasComplexChildren)
{
if (otherUtf8Text.Length < segment.Length / JsonConstants.MaxExpansionFactorWhileEscaping)
{
return false;
}

int idx = segment.IndexOf(JsonConstants.BackSlash);
Debug.Assert(idx != -1);

if (!otherUtf8Text.StartsWith(segment.Slice(0, idx)))
{
return false;
}

return JsonReaderHelper.UnescapeAndCompare(segment.Slice(idx), otherUtf8Text.Slice(idx));
}

return segment.SequenceEqual(otherUtf8Text);
}

internal string GetNameOfPropertyValue(int index)
{
// The property name is one row before the property value
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,12 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System;
using System.Buffers;
using System.Buffers.Text;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.InteropServices;

namespace System.Text.Json
{
Expand Down Expand Up @@ -972,6 +975,104 @@ internal string GetPropertyRawText()
return _parent.GetPropertyRawValueAsString(_idx);
}

/// <summary>
/// Compares <paramref name="text" /> to the string value of this element.
/// </summary>
/// <param name="text">The text to compare against.</param>
/// <returns>
/// <see langword="true" /> if the string value of this element matches <paramref name="text"/>,
/// <see langword="false" /> otherwise.
/// </returns>
/// <exception cref="InvalidOperationException">
/// This value's <see cref="Type"/> is not <see cref="JsonValueType.String"/>.
/// </exception>
/// <remarks>
/// This method is functionally equal to doing an ordinal comparison of <paramref name="text" /> and
/// the result of calling <see cref="GetString" />, but avoids creating the string instance.
/// </remarks>
public bool ValueEquals(string text)
{
// CheckValidInstance is done in the helper

if (TokenType == JsonTokenType.Null)
{
return text == null;
}

return TextEqualsHelper(text.AsSpan(), isPropertyName: false);
}

/// <summary>
/// Compares the text represented by <paramref name="utf8Text" /> to the string value of this element.
/// </summary>
/// <param name="utf8Text">The UTF-8 encoded text to compare against.</param>
/// <returns>
/// <see langword="true" /> if the string value of this element has the same UTF-8 encoding as
/// <paramref name="utf8Text" />, <see langword="false" /> otherwise.
/// </returns>
/// <exception cref="InvalidOperationException">
/// This value's <see cref="Type"/> is not <see cref="JsonValueType.String"/>.
/// </exception>
/// <remarks>
/// This method is functionally equal to doing an ordinal comparison of the string produced by UTF-8 decoding
/// <paramref name="utf8Text" /> with the result of calling <see cref="GetString" />, but avoids creating the
/// string instances.
/// </remarks>
public bool ValueEquals(ReadOnlySpan<byte> utf8Text)
{
// CheckValidInstance is done in the helper

if (TokenType == JsonTokenType.Null)
{
// This is different than Length == 0, in that it tests true for null, but false for ""
return utf8Text == default;
}

return TextEqualsHelper(utf8Text, isPropertyName: false);
}

/// <summary>
/// Compares <paramref name="text" /> to the string value of this element.
/// </summary>
/// <param name="text">The text to compare against.</param>
/// <returns>
/// <see langword="true" /> if the string value of this element matches <paramref name="text"/>,
/// <see langword="false" /> otherwise.
/// </returns>
/// <exception cref="InvalidOperationException">
/// This value's <see cref="Type"/> is not <see cref="JsonValueType.String"/>.
/// </exception>
/// <remarks>
/// This method is functionally equal to doing an ordinal comparison of <paramref name="text" /> and
/// the result of calling <see cref="GetString" />, but avoids creating the string instance.
/// </remarks>
public bool ValueEquals(ReadOnlySpan<char> text)
{
// CheckValidInstance is done in the helper

if (TokenType == JsonTokenType.Null)
{
// This is different than Length == 0, in that it tests true for null, but false for ""
return text == default;
}

return TextEqualsHelper(text, isPropertyName: false);
}

internal bool TextEqualsHelper(ReadOnlySpan<byte> utf8Text, bool isPropertyName)
{
CheckValidInstance();

return _parent.TextEquals(_idx, utf8Text, isPropertyName);
}

internal bool TextEqualsHelper(ReadOnlySpan<char> text, bool isPropertyName)
{
CheckValidInstance();

return _parent.TextEquals(_idx, text, isPropertyName);
}

/// <summary>
/// Write the element into the provided writer as a named object property.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System;

namespace System.Text.Json
{
/// <summary>
Expand All @@ -24,6 +26,66 @@ internal JsonProperty(JsonElement value)
/// </summary>
public string Name => Value.GetPropertyName();

/// <summary>
/// Compares <paramref name="text" /> to the name of this property.
/// </summary>
/// <param name="text">The text to compare against.</param>
/// <returns>
/// <see langword="true" /> if the name of this property matches <paramref name="text"/>,
/// <see langword="false" /> otherwise.
/// </returns>
/// <exception cref="InvalidOperationException">
/// This value's <see cref="Type"/> is not <see cref="JsonTokenType.PropertyName"/>.
/// </exception>
/// <remarks>
/// This method is functionally equal to doing an ordinal comparison of <paramref name="text" /> and
/// <see cref="Name" />, but can avoid creating the string instance.
/// </remarks>
public bool NameEquals(string text)
{
return NameEquals(text.AsSpan());
}

/// <summary>
/// Compares the text represented by <paramref name="utf8Text" /> to the name of this property.
/// </summary>
/// <param name="utf8Text">The UTF-8 encoded text to compare against.</param>
/// <returns>
/// <see langword="true" /> if the name of this property has the same UTF-8 encoding as
/// <paramref name="utf8Text" />, <see langword="false" /> otherwise.
/// </returns>
/// <exception cref="InvalidOperationException">
/// This value's <see cref="Type"/> is not <see cref="JsonTokenType.PropertyName"/>.
/// </exception>
/// <remarks>
/// This method is functionally equal to doing an ordinal comparison of <paramref name="utf8Text" /> and
/// <see cref="Name" />, but can avoid creating the string instance.
/// </remarks>
public bool NameEquals(ReadOnlySpan<byte> utf8Text)
{
return Value.TextEqualsHelper(utf8Text, isPropertyName: true);
}

/// <summary>
/// Compares <paramref name="text" /> to the name of this property.
/// </summary>
/// <param name="text">The text to compare against.</param>
/// <returns>
/// <see langword="true" /> if the name of this property matches <paramref name="text"/>,
/// <see langword="false" /> otherwise.
/// </returns>
/// <exception cref="InvalidOperationException">
/// This value's <see cref="Type"/> is not <see cref="JsonTokenType.PropertyName"/>.
/// </exception>
/// <remarks>
/// This method is functionally equal to doing an ordinal comparison of <paramref name="text" /> and
/// <see cref="Name" />, but can avoid creating the string instance.
/// </remarks>
public bool NameEquals(ReadOnlySpan<char> text)
{
return Value.TextEqualsHelper(text, isPropertyName: true);
}

/// <summary>
/// Provides a <see cref="string"/> representation of the property for
/// debugging purposes.
Expand Down
Loading

0 comments on commit ab1d04e

Please sign in to comment.