簡體   English   中英

使用 c# 中的 Newtonsoft 對來自 Json 的嵌套對象進行有效的手動反序列化

[英]Efficient Manual Deserialization of Nested objects from Json using Newtonsoft in c#

我有這個網格,我想盡快從 json 中讀取。

[Serializable]
public class Mesh
{
    public int[] Faces { get; set; }
    public Vec3[] Vertices { get; set; }
}

[Serializable]
public class Vec3
{
    public double X { get; set; }
    public double Y { get; set; }
    public double Z { get; set; }
}

我讀過創建手動序列化器和反序列化器比使用反射快得多 所以我嘗試創建自己的:

public static Mesh FromJson(JsonTextReader reader)
{
    var mesh = new Mesh();
    var currentProperty = string.Empty;
    List<int> faces = new List<int>();
    List<Vec3> Vertices = new List<Vec3>();
    double X = 0, Y = 0, Z = 0;
    while (reader.Read())
    {
        if (reader.Value != null)
        {
            if (reader.TokenType == JsonToken.PropertyName)
                currentProperty = reader.Value.ToString();

            else if (reader.TokenType == JsonToken.Integer && currentProperty == "Faces")
                faces.Add(Int32.Parse(reader.Value.ToString()));

            else if (reader.TokenType == JsonToken.Float && currentProperty == "X")
            {
                X = float.Parse(reader.Value.ToString());
            }

            else if (reader.TokenType == JsonToken.Float && currentProperty == "Y")
            {
                Y = float.Parse(reader.Value.ToString());
            }

            else if (reader.TokenType == JsonToken.Float && currentProperty == "Z")
            {
                Z = float.Parse(reader.Value.ToString());
            }
        }
        else
        {
            //Console.WriteLine("Token: {0}", reader.TokenType);

            if (reader.TokenType == JsonToken.EndObject && reader.Path.Contains("Vertices"))
                Vertices.Add(new Vec3 { X = X, Y = Y, Z = Z });

        }
    }

    mesh.Faces = faces.ToArray();
    mesh.Vertices = Vertices.ToArray();
    return mesh;
}

盡管我在寫作時設法獲得了 30% 的良好改進(期待更多的 TBH),但閱讀給我帶來了麻煩。 我覺得這與Vec3的嵌套有關,因為如果我運行類似的邏輯但沒有Vec3 ,我會得到大約 30% 的不錯結果。 我能找到的唯一例子是處理沒有嵌套的簡單數據結構,所以我覺得我在這里處理它有點簡單。

將自動反序列化替換為手動反序列化時,速度提高 30% 並不意外。 Json.NET將所有反射結果緩存在其合約解析器中,因此反射的開銷並不像您想象的那么糟糕。 為給定類型構建合約會產生一次性懲罰,但如果您要反序列化一個大文件,則懲罰會被攤銷,並且生成的合約為快速獲取和設置屬性值提供了委托。

話雖如此,我發現您的代碼存在以下問題(錯誤和可能的優化):

  1. 當您閱讀 JSON 時,您沒有跟蹤 state 的解析。 這需要您執行不高效的reader.Path.Contains("Vertices") 當 JSON 數據不符合預期時,它還會使您的代碼容易受到意外行為的影響。

  2. 您正在檢查currentProperty的字符串相等性,但是如果要將所有預期的屬性名稱添加到DefaultJsonNameTable並將其設置為JsonTextReader.PropertyNameTable您將能夠用引用相等性檢查替換這些檢查,從而節省一些時間和一些 memory 分配.

    請注意,在 Json.NET 12.0.1中添加了 PropertyNameTable。

  3. XYZ是雙精度數,但您將它們解析為浮點數

     X = float.Parse(reader.Value.ToString());

    這是一個會導致准確性損失的錯誤。 更重要的是,您在當前文化(可能有本地化的小數分隔符)而不是不變文化中解析它們,這是另一個錯誤。

  4. 無論如何,無需將reader.Value解析為double ,因為它已經應該是double 對於 integer 值,它應該已經是long 簡單地轉換為所需的原始類型應該足夠且更快。

  5. 禁用自動日期識別可能會為您節省一些時間。

以下版本的FromJson()解決了這些問題:

public static partial class MeshExtensions
{
    const string Vertices = "Vertices";
    const string Faces = "Faces";
    const string X = "X";
    const string Y = "Y";
    const string Z = "Z";

    public static Mesh FromJson(JsonTextReader reader)
    {
        var nameTable = new DefaultJsonNameTable();
        nameTable.Add(Vertices);
        nameTable.Add(Faces);
        nameTable.Add(X);
        nameTable.Add(Y);
        nameTable.Add(Z);
        reader.PropertyNameTable = nameTable;  // For performance
        reader.DateParseHandling = DateParseHandling.None;  // Possibly for performance.

        bool verticesFound = false;
        List<Vec3> vertices = null;
        bool facesFound = false;
        List<int> faces = null;

        while (reader.ReadToContent())
        {
            if (reader.TokenType == JsonToken.PropertyName && reader.Value == (object)Vertices)
            {
                if (verticesFound)
                    throw new JsonSerializationException("Multiple vertices");
                reader.ReadToContentAndAssert(); // Advance past the property name
                vertices = ReadVertices(reader); // Read the vertices array
                verticesFound = true;
            }
            else if (reader.TokenType == JsonToken.PropertyName && reader.Value == (object)Faces)
            {
                if (facesFound)
                    throw new JsonSerializationException("Multiple faces");
                reader.ReadToContentAndAssert(); // Advance past the property name
                faces = reader.ReadIntArray(); // Read the vertices array
                facesFound = true;
            }
        }

        return new Mesh
        {
            Vertices = vertices == null ? null : vertices.ToArray(),
            Faces = faces == null ? null : faces.ToArray(),
        };
    }

    static List<Vec3> ReadVertices(JsonTextReader reader)
    {
        if (reader.MoveToContentAndAssert().TokenType == JsonToken.Null)
            return null;
        else if (reader.TokenType != JsonToken.StartArray)
            throw new JsonSerializationException(string.Format("Unexpected token type {0}", reader.TokenType));
        var vertices = new List<Vec3>();
        while (reader.ReadToContent())
        {
            switch (reader.TokenType)
            {
                case JsonToken.EndArray:
                    return vertices;

                case JsonToken.Null:
                    // Or throw an exception if you prefer.
                    //throw new JsonSerializationException(string.Format("Unexpected token type {0}", reader.TokenType));
                    vertices.Add(null);
                    break;

                case JsonToken.StartObject:
                    var vertex = ReadVertex(reader);
                    vertices.Add(vertex);
                    break;

                default:
                    // reader.Skip();
                    throw new JsonSerializationException(string.Format("Unexpected token type {0}", reader.TokenType));
            }
        }
        throw new JsonReaderException(); // Truncated file.
    }

    static Vec3 ReadVertex(JsonTextReader reader)
    {
        if (reader.MoveToContentAndAssert().TokenType == JsonToken.Null)
            return null;
        else if (reader.TokenType != JsonToken.StartObject)
            throw new JsonException();
        var vec = new Vec3();
        while (reader.ReadToContent())
        {
            switch (reader.TokenType)
            {
                case JsonToken.EndObject:
                    return vec;

                case JsonToken.PropertyName:
                    if (reader.Value == (object)X)
                        vec.X = reader.ReadAsDouble().Value;
                    else if (reader.Value == (object)Y)
                        vec.Y = reader.ReadAsDouble().Value;
                    else if (reader.Value == (object)Z)
                        vec.Z = reader.ReadAsDouble().Value;
                    else // Skip unknown property names and values.
                        reader.ReadToContentAndAssert().Skip();
                    break;

                default:
                    throw new JsonSerializationException(string.Format("Unexpected token type {0}", reader.TokenType));
            }
        }
        throw new JsonReaderException(); // Truncated file.
    }
}

public static class JsonExtensions
{
    public static List<int> ReadIntArray(this JsonReader reader)
    {
        if (reader.MoveToContentAndAssert().TokenType == JsonToken.Null)
            return null;
        else if (reader.TokenType != JsonToken.StartArray)
            throw new JsonReaderException(string.Format("Unexpected token type {0}", reader.TokenType));

        var list = new List<int>();
        // ReadAsInt32() reads the next token as an integer, skipping comments
        for (var value = reader.ReadAsInt32(); true; value = reader.ReadAsInt32())
        {
            if (value != null)
                list.Add(value.Value);
            else 
                // value can be null if we reached the end of the array, encountered a null value, or encountered the end of a truncated file.
                // JsonReader will throw an exception on most types of malformed file, but not on a truncated file.
                switch (reader.TokenType)
                {
                    case JsonToken.EndArray:
                        return list;
                    case JsonToken.Null:
                    default:
                        throw new JsonReaderException(string.Format("Unexpected token type {0}", reader.TokenType));
                }
        }
    }

    public static bool ReadToContent(this JsonReader reader)
    {
        if (reader == null)
            throw new ArgumentNullException();
        if (!reader.Read())
            return false;
        while (reader.TokenType == JsonToken.Comment) // Skip past comments.
            if (!reader.Read())
                return false;
        return true;
    }

    public static JsonReader ReadToContentAndAssert(this JsonReader reader)
    {
        return reader.ReadAndAssert().MoveToContentAndAssert();
    }

    public static JsonReader MoveToContentAndAssert(this JsonReader reader)
    {
        if (reader == null)
            throw new ArgumentNullException();
        if (reader.TokenType == JsonToken.None)       // Skip past beginning of stream.
            reader.ReadAndAssert();
        while (reader.TokenType == JsonToken.Comment) // Skip past comments.
            reader.ReadAndAssert();
        return reader;
    }

    public static JsonReader ReadAndAssert(this JsonReader reader)
    {
        if (reader == null)
            throw new ArgumentNullException();
        if (!reader.Read())
            throw new JsonReaderException("Unexpected end of JSON stream.");
        return reader;
    }
}

演示小提琴在這里 正如您所看到的,正確處理諸如注釋、意外屬性和截斷流等邊界條件會使編寫健壯的手動反序列化代碼變得棘手。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM