simd-json

repository·main·Indexed 23 days ago

https://github.com/simd-lite/simd-json

A high-performance Rust port of the simdjson JSON parser, version 0.17.3. It utilizes SIMD instructions and provides Serde compatibility for fast JSON parsing and deserialization. The library features a Tape-based DOM structure, reusable Buffers to minimize allocations, and AlignedBuf for SIMD-aligned memory management. It supports multiple architectures including SSE4.2, AVX2, NEON, and WASM SIMD128.

Tokens
6.7K
Snippets
11
Records
52
Agent score
79%

What's inside simd-json

  1. Coding guidelines for simd-json

    main

    When writing code for this project, adhere to the following standards:

    • Style: Follow Rust's official style guide and best practices.
    • Documentation: Use comments to provide clear explanations for complex logic.
    • Idiomatic Rust: Aim for efficient and idiomatic Rust code.
    • Serde Compatibility: Ensure all code is compatible with serde for JSON serialization and deserialization.
  2. Prerequisites for contributing to simd-json

    main

    Before contributing to the Rust port of simd-json, ensure your environment meets the following requirements:

    • Rust and Cargo: Must be installed on your system.
    • SIMD Capability: Your system must be SIMD-capable to take advantage of the parser's performance.
    • GitHub Account: Required for version control and issue tracking.
    • Project Familiarity: Review the contents of the example folder in the repository to understand the project's goals and usage patterns.
  3. Testing requirements

    main

    Comprehensive test coverage is required for all contributions. You must:

    • Write unit tests for any new code you introduce.
    • Ensure that all existing tests pass.
    • Rely on continuous integration (CI) tools to verify compatibility across various Rust versions.
  4. Workflow for code contributions

    main

    To contribute code to simd-json, follow these steps:

    1. Fork the project repository on GitHub.
    2. Clone your fork locally.
    3. Create a branch for your changes (e.g., feature/your-feature).
    4. Implement code following Rust idiomatic practices and ensuring compatibility with serde for JSON serialization/deserialization.
    5. Write unit tests for your changes.
    6. Reference data: You can refer to the data folder for additional information during development.
    7. Push your changes to your GitHub fork.
    8. Create a Pull Request (PR) with a clear description and links to relevant issues.
    git clone https://github.com/your-username/your-repo.git
    git checkout -b feature/your-feature
    # ... write code and tests ...
    git push origin feature/your-feature
  5. How the Lazy Value API works

    main

    The Value type provides a lazy implementation of the JSON DOM. It starts as a Tape variant, which allows for extremely cheap parsing and data access by deferring full deserialization.

    As long as you only perform non-mutating operations, the value remains a Tape. If you perform a mutating operation (like insert), the Value automatically "upgrades" itself to a borrowed Value (using a Cow internally) to support the changes. This pattern allows you to benefit from high-performance lazy access while still maintaining the ability to modify the JSON structure when necessary.

    use simd_json::{prelude::*, value::lazy::Value};
    
    let mut json = br#"{"key": "value", "snot": 42}"# .to_vec();
    let tape = simd_json::to_tape( json.as_mut_slice()).unwrap();
    let value = tape.as_value();
    let mut lazy = Value::from_tape(value);
    
    assert_eq!(lazy.get("key").unwrap(), "value");
    
    assert!(lazy.is_tape());
    // Mutating the value triggers an upgrade from Tape to Value
    lazy.insert("new", 42);
    assert!(lazy.is_value());
    assert_eq!(lazy.get("key").unwrap(), "value");
    assert_eq!(lazy.get("new").unwrap(), 42);
  6. Understand the difference between BorrowedValue and OwnedValue

    main

    The library provides two primary DOM (Document Object Model) implementations for interacting with parsed JSON:

    1. BorrowedValue: Uses &str for strings, referencing the original input slice. While it does not perform full zero-copy parsing (because it must de-escape strings in-place), it avoids allocating new memory for string content. This is ideal for performance when the input buffer can be mutated and its lifetime is managed.
    2. OwnedValue: A 'lifetimeless' version where each string value allocates a new String. Use this when you need to move the parsed data across different parts of your application without worrying about the original input buffer's lifetime.

    Both implementations can be interacted with using the value_trait methods for inspection and mutation.

  7. Access and modify JSON data using Owned Value

    main

    The Value type provides an owned JSON DOM that allows for easy access and modification of data using indexing. It is slower than BorrowedValue but avoids lifetime complexities.

    • Array Access: Use usize indexing to access or mutate array elements.
    • Object Access: Use &str indexing to access or mutate object values by key.

    Note: Indexing with [] will panic if the index is out of bounds or the key does not exist. For safe access, use the ValueTrait methods (like as_array, as_object, etc.) or the get methods provided by the traits.

    use simd_json::{OwnedValue, json};
    use simd_json::prelude::*;
    
    // Access via array indexes
    let mut a = json!([1, 2, 3]);
    assert_eq!(a[1], 2);
    a[1] = 42.into();
    assert_eq!(a[1], 42);
    
    // Access via object keys
    let mut b = json!({"key": "not the value"});
    assert_eq!(b["key"], "not the value");
    b["key"] = "value".into();
    assert_eq!(b["key"], "value");
  8. Manipulate JSON objects and arrays with OwnedValue

    main

    When using OwnedValue, you can treat JSON objects like HashMaps and JSON arrays like Vectors. This allows for easy creation, insertion, and nested mutation.

    Object usage:

    • v.insert(key, value): Inserts a key-value pair.
    • v.get(key): Returns an option to the value.
    • v.remove(key): Removes a key and returns its value.
    • v[key]: Indexing access.

    Array usage:

    • v.push(value): Appends an element.
    • v.pop(): Removes and returns the last element.
    • v.get_idx(index): Returns an option to the element at the specified index.
    • v[index]: Indexing access.
  9. Concept: Borrowed Value vs. Owned Value

    main

    The Value<'value> type is a borrowed DOM. It is designed for maximum performance by referencing the original input byte slice instead of allocating new memory for strings and keys.

    Key Characteristics:

    • Performance: Extremely fast and low-allocation because it uses Cow<'value, str> to point into the input buffer.
    • Lifetimes: The Value is tied to the lifetime of the input buffer. You cannot return a Value<'a> from a function if the buffer was created inside that function.
    • Mutability: The input buffer must be &mut [u8] because simd-json performs in-place de-escaping of strings.

    When to use which:

    • Use Value<'value> (Borrowed) when you are parsing data immediately and want to minimize overhead.
    • Use into_static() or convert to an owned representation when you need the JSON data to outlive the input buffer.
  10. Deserialize JSON into Rust types using Serde

    main

    Use simd-json to deserialize JSON directly into Rust structs or types that implement serde::Deserialize.

    Important Performance Note: If you are parsing into a DOM (Document Object Model), you should use to_owned_value or to_borrowed_value instead of Serde, as they provide significantly better performance. Use Serde primarily when parsing directly to specific structs or when required by other parts of your application.

    Note on Mutability: Most deserialization functions in simd-json require a mutable slice (&mut [u8]) or string (&mut str) because the parser rewrites the input in-place to improve performance. This may result in the input no longer being valid UTF-8 after the call.

  11. Convert Owned Value to scalar types

    main

    To extract primitive values from an Owned Value, use the methods provided by the ValueAsScalar trait. These methods return an Option if the type matches or if the value is a StaticNode containing the requested type.

    Supported scalar conversions:

    • as_null() -> Option<()>
    • as_bool() -> Option<bool>
    • as_i64() -> Option<i64>
    • as_i128() -> Option<i128>
    • as_u64() -> Option<u64>
    • as_u128() -> Option<u128>
    • as_f64() -> Option<f64>
    • as_str() -> Option<&str>
  12. Identify the parsing algorithm used

    main

    You can query the Deserializer to determine which SIMD implementation or architecture-specific algorithm is currently being used. This is useful for performance profiling or debugging.

    Supported Implementation variants include:

    • Native: Standard Rust implementation.
    • StdSimd: Using std::simd (portable SIMD).
    • SSE42: Intel/AMD SSE4.2.
    • AVX2: Intel/AMD AVX2.
    • NEON: ARM NEON.
    • SIMD128: WebAssembly SIMD128.