将对象转换为 System.Text.Json.JsonElement

Convert object to System.Text.Json.JsonElement

假设我有一个类型的对象:

public class MyClass
{
    public string Data { get; set; }
}

我需要将其转换为 System.Text.Json.JsonElement。我找到的唯一方法是:

var json = JsonSerializer.Serialize(new MyClass { Data = "value" });

using var document = JsonDocument.Parse(json);

var jsonElement = document.RootElement;

似乎很奇怪,我必须先序列化它然后再解析它。有更好的方法吗?

之前我使用的是 Newtonsoft.Json 中的 JObject,我可以这样做:

var jobject = JObject.FromObject(new MyClass { Data = "value" });

在 .NET 6 中 方法被添加到 JsonSerializer 以将对象直接序列化为 JsonElement or JsonDocument:

public static partial class JsonSerializer
{
    public static JsonDocument SerializeToDocument<TValue>(TValue value, JsonSerializerOptions? options = null);
    public static JsonDocument SerializeToDocument(object? value, Type inputType, JsonSerializerOptions? options = null);
    public static JsonDocument SerializeToDocument<TValue>(TValue value, JsonTypeInfo<TValue> jsonTypeInfo);
    public static JsonDocument SerializeToDocument(object? value, Type inputType, JsonSerializerContext context);

    public static JsonElement SerializeToElement<TValue>(TValue value, JsonSerializerOptions? options = null);
    public static JsonElement SerializeToElement(object? value, Type inputType, JsonSerializerOptions? options = null);
    public static JsonElement SerializeToElement<TValue>(TValue value, JsonTypeInfo<TValue> jsonTypeInfo);
    public static JsonElement SerializeToElement(object? value, Type inputType, JsonSerializerContext context);
}

因此在 .NET 6 中你将能够做到:

using var jsonDocument = JsonSerializer.SerializeToDocument(new MyClass { Data = "value" });

var jsonElement = JsonSerializer.SerializeToElement(new MyClass { Data = "value" });

备注:

  • JsonSerializerContext and JsonTypeInfo<T> are newly exposed in .NET 6 and provide metadata about a set of types, or a single type T, that is relevant to JSON serialization. They are used when serializing using metadata and code generated at compile time. See Try the new System.Text.Json source generator 了解详情。

  • JsonDocumentIDisposable,实际上必须要处理掉,因为根据docs:

    JsonDocument builds an in-memory view of the data into a pooled buffer. Therefore, unlike JObject or JArray from Newtonsoft.Json, the JsonDocument type implements IDisposable and needs to be used inside a using block.

    在您的示例代码中,您没有处理 JsonDocument.Parse() 返回的文档,但您应该处理。

  • 新方法应该出现在 .NET 6 RC1

在 .NET 5 和更早版本中 等同于 JObject.FromObject() 的方法目前在 System.Text.Json 中不可用。关于此有一个开放的增强功能,目前针对 Future:

在此期间,您可以通过序列化为中间 byte 数组而不是字符串来获得更好的性能,因为 JsonDocumentUtf8JsonReader 都直接与 [=33= 一起工作] 跨度而不是字符串或 char 跨度,如下所示:

public static partial class JsonExtensions
{
    public static JsonDocument JsonDocumentFromObject<TValue>(TValue value, JsonSerializerOptions options = default) 
        => JsonDocumentFromObject(value, typeof(TValue), options);

    public static JsonDocument JsonDocumentFromObject(object value, Type type, JsonSerializerOptions options = default)
    {
        var bytes = JsonSerializer.SerializeToUtf8Bytes(value, type, options);
        return JsonDocument.Parse(bytes);
    }

    public static JsonElement JsonElementFromObject<TValue>(TValue value, JsonSerializerOptions options = default)
        => JsonElementFromObject(value, typeof(TValue), options);

    public static JsonElement JsonElementFromObject(object value, Type type, JsonSerializerOptions options = default)
    {
        using var doc = JsonDocumentFromObject(value, type, options);
        return doc.RootElement.Clone();
    }
}

然后这样称呼它:

using var doc = JsonExtensions.JsonDocumentFromObject(new MyClass { Data = "value" });

或者,如果您需要在 using 语句的范围之外使用根元素:

var element = JsonExtensions.JsonElementFromObject(new MyClass { Data = "value" });

备注:

  • 如上所述,JsonDocument创建后需要释放。 上述 JsonExtensions.JsonElementFromObject() 扩展方法正确处理了它们的内部文档和 returns 根元素的克隆,如 documentation.

    中所推荐
  • 序列化为中间 Utf8 字节序列可能比序列化为 string 更高效,因为根据 docs:

    Serializing to UTF-8 is about 5-10% faster than using the string-based methods. The difference is because the bytes (as UTF-8) don't need to be converted to strings (UTF-16).

  • 反演方法见System.Text.Json.JsonElement ToObject workaround.

演示 fiddle here.

dbc 的回答是一个好的开始,但如果对象值已经是 json 字符串,则还不够! 而且,他的代码中并没有使用该类型。

因此我提出以下改进版本:

    public static JsonDocument JsonDocumentFromObject(object value, JsonSerializerOptions options = null)
    {
        if (value is string valueStr)
        {
            try { return JsonDocument.Parse(valueStr); }
            catch {}
        }

        byte[] bytes = JsonSerializer.SerializeToUtf8Bytes(value, options);
        return JsonDocument.Parse(bytes);
    }

    public static JsonElement JsonElementFromObject(object value, JsonSerializerOptions options = null)
    {
        JsonElement result;
        using (JsonDocument doc = JsonDocumentFromObject(value, options))
        {
            result = doc.RootElement.Clone();
        }
        return result;
    }

使用以下单元测试 (xUnit):

    [Fact()]
    public void JsonElementFromObjectTest()
    {
        object o = new
        {
            id = "myId",
            timestamp = DateTime.UtcNow,
            valid = true,
            seq = 1
        };

        JsonElement element1 = JsonSerializerExtension.JsonElementFromObject(o);
        Assert.Equal(JsonValueKind.Object, element1.ValueKind);
        string oStr1 = element1.GetRawText();
        Assert.NotNull(oStr1);

        JsonElement element2 = JsonSerializerExtension.JsonElementFromObject(oStr1);
        Assert.Equal(JsonValueKind.Object, element2.ValueKind);
        string oStr2 = element2.GetRawText();
        Assert.NotNull(oStr2);

        Assert.Equal(oStr1, oStr2);
    }

如果没有直接尝试解析,element2 是一个 JsonValueKind.String 并且 oStr2 包含未转义的 unicode 字符,因此是一个无效的 Json 字符串。

.NET 5 中的巧妙方法是:

private JsonElement JsonElementFromObject(object value)
{
    var jsonUtf8Bytes = JsonSerializer.SerializeToUtf8Bytes(value, new JsonSerializerOptions());
    using var doc = JsonDocument.Parse(jsonUtf8Bytes);
    return doc.RootElement.Clone();
}

步骤:

  1. 将值转换为 JSON 字符串,编码为 UTF-8 字节 (SerializeToUtf8Bytes)。
  2. 解析 JSON 字符串 (JsonDocument.Parse)。
  3. Return 根元素。