|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Buffers;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Text.Json.Serialization;
using System.Text.Json.Serialization.Metadata;
namespace System.Text.Json
{
/// <summary>
/// Provides functionality to serialize objects or value types to JSON and
/// deserialize JSON into objects or value types.
/// </summary>
public static partial class JsonSerializer
{
/// <summary>
/// Parses the text representing a single JSON value into a <typeparamref name="TValue"/>.
/// </summary>
/// <typeparam name="TValue">The type to deserialize the JSON value into.</typeparam>
/// <returns>A <typeparamref name="TValue"/> representation of the JSON value.</returns>
/// <param name="json">JSON text to parse.</param>
/// <param name="options">Options to control the behavior during parsing.</param>
/// <exception cref="System.ArgumentNullException">
/// <paramref name="json"/> is <see langword="null"/>.
/// </exception>
/// <exception cref="JsonException">
/// The JSON is invalid.
///
/// -or-
///
/// <typeparamref name="TValue" /> is not compatible with the JSON.
///
/// -or-
///
/// There is remaining data in the string beyond a single JSON value.</exception>
/// <exception cref="NotSupportedException">
/// There is no compatible <see cref="System.Text.Json.Serialization.JsonConverter"/>
/// for <typeparamref name="TValue"/> or its serializable members.
/// </exception>
/// <remarks>Using a <see cref="string"/> is not as efficient as using the
/// UTF-8 methods since the implementation natively uses UTF-8.
/// </remarks>
[RequiresUnreferencedCode(SerializationUnreferencedCodeMessage)]
[RequiresDynamicCode(SerializationRequiresDynamicCodeMessage)]
public static TValue? Deserialize<TValue>([StringSyntax(StringSyntaxAttribute.Json)] string json, JsonSerializerOptions? options = null)
{
if (json is null)
{
ThrowHelper.ThrowArgumentNullException(nameof(json));
}
JsonTypeInfo<TValue> jsonTypeInfo = GetTypeInfo<TValue>(options);
return ReadFromSpan(json.AsSpan(), jsonTypeInfo);
}
/// <summary>
/// Parses the text representing a single JSON value into an instance of the type specified by a generic type parameter.
/// </summary>
/// <typeparam name="TValue">The type to deserialize the JSON value into.</typeparam>
/// <returns>A <typeparamref name="TValue"/> representation of the JSON value.</returns>
/// <param name="json">The JSON text to parse.</param>
/// <param name="options">Options to control the behavior during parsing.</param>
/// <exception cref="JsonException">
/// The JSON is invalid.
///
/// -or-
///
/// <typeparamref name="TValue" /> is not compatible with the JSON.
///
/// -or-
///
/// There is remaining data in the span beyond a single JSON value.</exception>
/// <exception cref="NotSupportedException">
/// There is no compatible <see cref="System.Text.Json.Serialization.JsonConverter"/>
/// for <typeparamref name="TValue"/> or its serializable members.
/// </exception>
/// <remarks>Using a UTF-16 span is not as efficient as using the
/// UTF-8 methods since the implementation natively uses UTF-8.
/// </remarks>
[RequiresUnreferencedCode(SerializationUnreferencedCodeMessage)]
[RequiresDynamicCode(SerializationRequiresDynamicCodeMessage)]
public static TValue? Deserialize<TValue>([StringSyntax(StringSyntaxAttribute.Json)] ReadOnlySpan<char> json, JsonSerializerOptions? options = null)
{
JsonTypeInfo<TValue> jsonTypeInfo = GetTypeInfo<TValue>(options);
return ReadFromSpan(json, jsonTypeInfo);
}
/// <summary>
/// Parses the text representing a single JSON value into a <paramref name="returnType"/>.
/// </summary>
/// <returns>A <paramref name="returnType"/> representation of the JSON value.</returns>
/// <param name="json">JSON text to parse.</param>
/// <param name="returnType">The type of the object to convert to and return.</param>
/// <param name="options">Options to control the behavior during parsing.</param>
/// <exception cref="System.ArgumentNullException">
/// <paramref name="json"/> or <paramref name="returnType"/> is <see langword="null"/>.
/// </exception>
/// <exception cref="JsonException">
/// The JSON is invalid.
///
/// -or-
///
/// <paramref name="returnType"/> is not compatible with the JSON.
///
/// -or-
///
/// There is remaining data in the string beyond a single JSON value.</exception>
/// <exception cref="NotSupportedException">
/// There is no compatible <see cref="System.Text.Json.Serialization.JsonConverter"/>
/// for <paramref name="returnType"/> or its serializable members.
/// </exception>
/// <remarks>Using a <see cref="string"/> is not as efficient as using the
/// UTF-8 methods since the implementation natively uses UTF-8.
/// </remarks>
[RequiresUnreferencedCode(SerializationUnreferencedCodeMessage)]
[RequiresDynamicCode(SerializationRequiresDynamicCodeMessage)]
public static object? Deserialize([StringSyntax(StringSyntaxAttribute.Json)] string json, Type returnType, JsonSerializerOptions? options = null)
{
if (json is null)
{
ThrowHelper.ThrowArgumentNullException(nameof(json));
}
if (returnType is null)
{
ThrowHelper.ThrowArgumentNullException(nameof(returnType));
}
JsonTypeInfo jsonTypeInfo = GetTypeInfo(options, returnType);
return ReadFromSpanAsObject(json.AsSpan(), jsonTypeInfo);
}
/// <summary>
/// Parses the text representing a single JSON value into an instance of a specified type.
/// </summary>
/// <returns>A <paramref name="returnType"/> representation of the JSON value.</returns>
/// <param name="json">The JSON text to parse.</param>
/// <param name="returnType">The type of the object to convert to and return.</param>
/// <param name="options">Options to control the behavior during parsing.</param>
/// <exception cref="System.ArgumentNullException">
/// <paramref name="returnType"/> is <see langword="null"/>.
/// </exception>
/// <exception cref="JsonException">
/// The JSON is invalid.
///
/// -or-
///
/// <paramref name="returnType"/> is not compatible with the JSON.
///
/// -or-
///
/// There is remaining data in the span beyond a single JSON value.</exception>
/// <exception cref="NotSupportedException">
/// There is no compatible <see cref="System.Text.Json.Serialization.JsonConverter"/>
/// for <paramref name="returnType"/> or its serializable members.
/// </exception>
/// <remarks>Using a UTF-16 span is not as efficient as using the
/// UTF-8 methods since the implementation natively uses UTF-8.
/// </remarks>
[RequiresUnreferencedCode(SerializationUnreferencedCodeMessage)]
[RequiresDynamicCode(SerializationRequiresDynamicCodeMessage)]
public static object? Deserialize([StringSyntax(StringSyntaxAttribute.Json)] ReadOnlySpan<char> json, Type returnType, JsonSerializerOptions? options = null)
{
if (returnType is null)
{
ThrowHelper.ThrowArgumentNullException(nameof(returnType));
}
// default/null span is treated as empty
JsonTypeInfo jsonTypeInfo = GetTypeInfo(options, returnType);
return ReadFromSpanAsObject(json, jsonTypeInfo);
}
/// <summary>
/// Parses the text representing a single JSON value into a <typeparamref name="TValue"/>.
/// </summary>
/// <typeparam name="TValue">The type to deserialize the JSON value into.</typeparam>
/// <returns>A <typeparamref name="TValue"/> representation of the JSON value.</returns>
/// <param name="json">JSON text to parse.</param>
/// <param name="jsonTypeInfo">Metadata about the type to convert.</param>
/// <exception cref="System.ArgumentNullException">
/// <paramref name="json"/> is <see langword="null"/>.
///
/// -or-
///
/// <paramref name="jsonTypeInfo"/> is <see langword="null"/>.
/// </exception>
/// <exception cref="JsonException">
/// The JSON is invalid.
///
/// -or-
///
/// <typeparamref name="TValue" /> is not compatible with the JSON.
///
/// -or-
///
/// There is remaining data in the string beyond a single JSON value.</exception>
/// <remarks>Using a <see cref="string"/> is not as efficient as using the
/// UTF-8 methods since the implementation natively uses UTF-8.
/// </remarks>
public static TValue? Deserialize<TValue>([StringSyntax(StringSyntaxAttribute.Json)] string json, JsonTypeInfo<TValue> jsonTypeInfo)
{
if (json is null)
{
ThrowHelper.ThrowArgumentNullException(nameof(json));
}
if (jsonTypeInfo is null)
{
ThrowHelper.ThrowArgumentNullException(nameof(jsonTypeInfo));
}
jsonTypeInfo.EnsureConfigured();
return ReadFromSpan(json.AsSpan(), jsonTypeInfo);
}
/// <summary>
/// Parses the text representing a single JSON value into a <typeparamref name="TValue"/>.
/// </summary>
/// <typeparam name="TValue">The type to deserialize the JSON value into.</typeparam>
/// <returns>A <typeparamref name="TValue"/> representation of the JSON value.</returns>
/// <param name="json">JSON text to parse.</param>
/// <param name="jsonTypeInfo">Metadata about the type to convert.</param>
/// <exception cref="System.ArgumentNullException">
/// <paramref name="json"/> is <see langword="null"/>.
///
/// -or-
///
/// <paramref name="jsonTypeInfo"/> is <see langword="null"/>.
/// </exception>
/// <exception cref="JsonException">
/// The JSON is invalid.
///
/// -or-
///
/// <typeparamref name="TValue" /> is not compatible with the JSON.
///
/// -or-
///
/// There is remaining data in the string beyond a single JSON value.</exception>
/// <remarks>Using a <see cref="string"/> is not as efficient as using the
/// UTF-8 methods since the implementation natively uses UTF-8.
/// </remarks>
public static TValue? Deserialize<TValue>([StringSyntax(StringSyntaxAttribute.Json)] ReadOnlySpan<char> json, JsonTypeInfo<TValue> jsonTypeInfo)
{
if (jsonTypeInfo is null)
{
ThrowHelper.ThrowArgumentNullException(nameof(jsonTypeInfo));
}
jsonTypeInfo.EnsureConfigured();
return ReadFromSpan(json, jsonTypeInfo);
}
/// <summary>
/// Parses the text representing a single JSON value into an instance specified by the <paramref name="jsonTypeInfo"/>.
/// </summary>
/// <returns>A <paramref name="jsonTypeInfo"/> representation of the JSON value.</returns>
/// <param name="json">JSON text to parse.</param>
/// <param name="jsonTypeInfo">Metadata about the type to convert.</param>
/// <exception cref="System.ArgumentNullException">
/// <paramref name="json"/> is <see langword="null"/>.
///
/// -or-
///
/// <paramref name="jsonTypeInfo"/> is <see langword="null"/>.
/// </exception>
/// <exception cref="JsonException">
/// The JSON is invalid.
///
/// -or-
///
/// There is remaining data in the string beyond a single JSON value.</exception>
/// <remarks>Using a <see cref="string"/> is not as efficient as using the
/// UTF-8 methods since the implementation natively uses UTF-8.
/// </remarks>
public static object? Deserialize([StringSyntax(StringSyntaxAttribute.Json)] string json, JsonTypeInfo jsonTypeInfo)
{
if (json is null)
{
ThrowHelper.ThrowArgumentNullException(nameof(json));
}
if (jsonTypeInfo is null)
{
ThrowHelper.ThrowArgumentNullException(nameof(jsonTypeInfo));
}
jsonTypeInfo.EnsureConfigured();
return ReadFromSpanAsObject(json.AsSpan(), jsonTypeInfo);
}
/// <summary>
/// Parses the text representing a single JSON value into an instance specified by the <paramref name="jsonTypeInfo"/>.
/// </summary>
/// <returns>A <paramref name="jsonTypeInfo"/> representation of the JSON value.</returns>
/// <param name="json">JSON text to parse.</param>
/// <param name="jsonTypeInfo">Metadata about the type to convert.</param>
/// <exception cref="System.ArgumentNullException">
/// <paramref name="jsonTypeInfo"/> is <see langword="null"/>.
/// </exception>
/// <exception cref="JsonException">
/// The JSON is invalid.
///
/// -or-
///
/// There is remaining data in the string beyond a single JSON value.</exception>
/// <remarks>Using a <see cref="string"/> is not as efficient as using the
/// UTF-8 methods since the implementation natively uses UTF-8.
/// </remarks>
public static object? Deserialize([StringSyntax(StringSyntaxAttribute.Json)] ReadOnlySpan<char> json, JsonTypeInfo jsonTypeInfo)
{
if (jsonTypeInfo is null)
{
ThrowHelper.ThrowArgumentNullException(nameof(jsonTypeInfo));
}
jsonTypeInfo.EnsureConfigured();
return ReadFromSpanAsObject(json, jsonTypeInfo);
}
/// <summary>
/// Parses the text representing a single JSON value into a <paramref name="returnType"/>.
/// </summary>
/// <returns>A <paramref name="returnType"/> representation of the JSON value.</returns>
/// <param name="json">JSON text to parse.</param>
/// <param name="returnType">The type of the object to convert to and return.</param>
/// <param name="context">A metadata provider for serializable types.</param>
/// <exception cref="System.ArgumentNullException">
/// <paramref name="json"/> or <paramref name="returnType"/> is <see langword="null"/>.
///
/// -or-
///
/// <paramref name="context"/> is <see langword="null"/>.
/// </exception>
/// <exception cref="JsonException">
/// The JSON is invalid.
///
/// -or-
///
/// <paramref name="returnType" /> is not compatible with the JSON.
///
/// -or-
///
/// There is remaining data in the string beyond a single JSON value.</exception>
/// <exception cref="NotSupportedException">
/// There is no compatible <see cref="System.Text.Json.Serialization.JsonConverter"/>
/// for <paramref name="returnType"/> or its serializable members.
/// </exception>
/// <exception cref="InvalidOperationException">
/// The <see cref="JsonSerializerContext.GetTypeInfo(Type)"/> method of the provided
/// <paramref name="context"/> returns <see langword="null"/> for the type to convert.
/// </exception>
/// <remarks>Using a <see cref="string"/> is not as efficient as using the
/// UTF-8 methods since the implementation natively uses UTF-8.
/// </remarks>
public static object? Deserialize([StringSyntax(StringSyntaxAttribute.Json)] string json, Type returnType, JsonSerializerContext context)
{
if (json is null)
{
ThrowHelper.ThrowArgumentNullException(nameof(json));
}
if (returnType is null)
{
ThrowHelper.ThrowArgumentNullException(nameof(returnType));
}
if (context is null)
{
ThrowHelper.ThrowArgumentNullException(nameof(context));
}
JsonTypeInfo jsonTypeInfo = GetTypeInfo(context, returnType);
return ReadFromSpanAsObject(json.AsSpan(), jsonTypeInfo);
}
/// <summary>
/// Parses the text representing a single JSON value into a <paramref name="returnType"/>.
/// </summary>
/// <returns>A <paramref name="returnType"/> representation of the JSON value.</returns>
/// <param name="json">JSON text to parse.</param>
/// <param name="returnType">The type of the object to convert to and return.</param>
/// <param name="context">A metadata provider for serializable types.</param>
/// <exception cref="System.ArgumentNullException">
/// <paramref name="json"/> or <paramref name="returnType"/> is <see langword="null"/>.
///
/// -or-
///
/// <paramref name="context"/> is <see langword="null"/>.
/// </exception>
/// <exception cref="JsonException">
/// The JSON is invalid.
///
/// -or-
///
/// <paramref name="returnType" /> is not compatible with the JSON.
///
/// -or-
///
/// There is remaining data in the string beyond a single JSON value.</exception>
/// <exception cref="NotSupportedException">
/// There is no compatible <see cref="System.Text.Json.Serialization.JsonConverter"/>
/// for <paramref name="returnType"/> or its serializable members.
/// </exception>
/// <exception cref="InvalidOperationException">
/// The <see cref="JsonSerializerContext.GetTypeInfo(Type)"/> method of the provided
/// <paramref name="context"/> returns <see langword="null"/> for the type to convert.
/// </exception>
/// <remarks>Using a <see cref="string"/> is not as efficient as using the
/// UTF-8 methods since the implementation natively uses UTF-8.
/// </remarks>
public static object? Deserialize([StringSyntax(StringSyntaxAttribute.Json)] ReadOnlySpan<char> json, Type returnType, JsonSerializerContext context)
{
if (returnType is null)
{
ThrowHelper.ThrowArgumentNullException(nameof(returnType));
}
if (context is null)
{
ThrowHelper.ThrowArgumentNullException(nameof(context));
}
JsonTypeInfo jsonTypeInfo = GetTypeInfo(context, returnType);
return ReadFromSpanAsObject(json, jsonTypeInfo);
}
private static TValue? ReadFromSpan<TValue>(ReadOnlySpan<char> json, JsonTypeInfo<TValue> jsonTypeInfo)
{
Debug.Assert(jsonTypeInfo.IsConfigured);
byte[]? tempArray = null;
// For performance, avoid obtaining actual byte count unless memory usage is higher than the threshold.
Span<byte> utf8 =
// Use stack memory
json.Length <= (JsonConstants.StackallocByteThreshold / JsonConstants.MaxExpansionFactorWhileTranscoding) ? stackalloc byte[JsonConstants.StackallocByteThreshold] :
// Use a pooled array
json.Length <= (JsonConstants.ArrayPoolMaxSizeBeforeUsingNormalAlloc / JsonConstants.MaxExpansionFactorWhileTranscoding) ? tempArray = ArrayPool<byte>.Shared.Rent(json.Length * JsonConstants.MaxExpansionFactorWhileTranscoding) :
// Use a normal alloc since the pool would create a normal alloc anyway based on the threshold (per current implementation)
// and by using a normal alloc we can avoid the Clear().
new byte[JsonReaderHelper.GetUtf8ByteCount(json)];
try
{
int actualByteCount = JsonReaderHelper.GetUtf8FromText(json, utf8);
utf8 = utf8.Slice(0, actualByteCount);
return ReadFromSpan(utf8, jsonTypeInfo, actualByteCount);
}
finally
{
if (tempArray != null)
{
utf8.Clear();
ArrayPool<byte>.Shared.Return(tempArray);
}
}
}
private static object? ReadFromSpanAsObject(ReadOnlySpan<char> json, JsonTypeInfo jsonTypeInfo)
{
Debug.Assert(jsonTypeInfo.IsConfigured);
byte[]? tempArray = null;
// For performance, avoid obtaining actual byte count unless memory usage is higher than the threshold.
Span<byte> utf8 =
// Use stack memory
json.Length <= (JsonConstants.StackallocByteThreshold / JsonConstants.MaxExpansionFactorWhileTranscoding) ? stackalloc byte[JsonConstants.StackallocByteThreshold] :
// Use a pooled array
json.Length <= (JsonConstants.ArrayPoolMaxSizeBeforeUsingNormalAlloc / JsonConstants.MaxExpansionFactorWhileTranscoding) ? tempArray = ArrayPool<byte>.Shared.Rent(json.Length * JsonConstants.MaxExpansionFactorWhileTranscoding) :
// Use a normal alloc since the pool would create a normal alloc anyway based on the threshold (per current implementation)
// and by using a normal alloc we can avoid the Clear().
new byte[JsonReaderHelper.GetUtf8ByteCount(json)];
try
{
int actualByteCount = JsonReaderHelper.GetUtf8FromText(json, utf8);
utf8 = utf8.Slice(0, actualByteCount);
return ReadFromSpanAsObject(utf8, jsonTypeInfo, actualByteCount);
}
finally
{
if (tempArray != null)
{
utf8.Clear();
ArrayPool<byte>.Shared.Return(tempArray);
}
}
}
}
}
|