dotnet / runtime

.NET is a cross-platform runtime for cloud, mobile, desktop, and IoT apps.
https://docs.microsoft.com/dotnet/core/
MIT License
15.13k stars 4.7k forks source link

[System.Text.Json] Consider adding a feature JsonSerializer to allow deserializing JsonElement #29500

Closed pranavkm closed 4 years ago

pranavkm commented 5 years ago

Probably the same as this - https://github.com/dotnet/corefx/issues/36169, but better worded. It would be useful to have an API that allows deserializing JsonElement in to "T" e.g.

JsonElement element = jsonDocument.Root[0];
SomePoco poco = JsonSerializer.Parse<SomePoco>(element);

Right now the only way to do this is to get to the raw text of the element which just results in needless allocations:

JsonElement element = jsonDocument.Root[0];
SomePoco poco = JsonSerializer.Parse<SomePoco>(element.GetRawText());
pranavkm commented 5 years ago

cc @ahsonkhan \ @JeremyKuhne \ @steveharter

ahsonkhan commented 5 years ago

Also cc @bartonjs - maybe this is additional motivation/reason to expose RawUtf8Bytes (as an alternative). Otherwise, it would be an internal API that the serializer would leverage.

Edit: The deserializer calling an onternal API should work just fine here.

peters commented 5 years ago

@ahsonkhan Allocation "free" version:

using System;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Text.Json;

namespace chaos.Serialization
{
    [SuppressMessage("ReSharper", "MemberCanBePrivate.Local")]
    [SuppressMessage("ReSharper", "UnusedMember.Local")]
    [SuppressMessage("ReSharper", "UnusedMember.Global")]
    internal readonly ref struct JsonElementSerializer
    {
        static readonly FieldInfo JsonDocumentField = typeof(JsonElement).GetField("_parent", BindingFlags.NonPublic | BindingFlags.Instance);
        static readonly FieldInfo JsonDocumentUtf8JsonField = typeof(JsonDocument).GetField("_utf8Json", BindingFlags.NonPublic | BindingFlags.Instance);

        ReadOnlyMemory<byte> Value { get; }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public JsonElementSerializer(JsonElement jsonElement)
        {
            if(JsonDocumentField == null) throw new ArgumentNullException(nameof(JsonDocumentField));
            if(JsonDocumentUtf8JsonField == null) throw new ArgumentNullException(nameof(JsonDocumentUtf8JsonField));
            var jsonDocument = JsonDocumentField.GetValue(jsonElement);
            Value = (ReadOnlyMemory<byte>) JsonDocumentUtf8JsonField.GetValue(jsonDocument);
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public T ToObject<T>(JsonSerializerOptions jsonSerializerOptions = null)
        {
            return (T) ToObject(typeof(T), jsonSerializerOptions);
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public object ToObject([JetBrains.Annotations.NotNull] Type type, JsonSerializerOptions jsonSerializerOptions = null)
        {
            if (type == null) throw new ArgumentNullException(nameof(type));
            return JsonSerializer.Deserialize(Value.Span, type, jsonSerializerOptions);
        }
    }
}
YohDeadfall commented 4 years ago

It makes sense to add a method to JsonElement to get a raw value. Shouldn't be hard because JsonDocument already has it:

public readonly struct JsonElement
{
    public ReadOnlyMemory<byte> GetRawValue()
    {
        CheckValidInstance();
    return _parent.GetRawValue(_idx, includeQuotes: true);
    }
}
bartonjs commented 4 years ago

@YohDeadfall We're intentionally not adding that method. JsonElement doesn't say Utf8 anywhere in the name, and we consider it to be encoding-agnostic. (JsonDocument can be built from a string, the fact that it transcodes is currently considered an implementation detail).

JsonElement.GetRawText is the compromise, it gives the input, but as a string. When Utf8String comes in, there'll be a Utf8String version.

layomia commented 4 years ago

Closing as duplicate of https://github.com/dotnet/corefx/issues/42056. The semantics of this feature are covered in the API proposal:

namespace System.Text.Json
{
    public static partial class JsonSerializer
    {
        public static TValue Deserialize<TValue>(this JsonDocument document);
        public static TValue Deserialize<TValue>(this JsonElement element);

        public static JsonDocument SerializeToDocument<TValue>(TValue value);
    }
}