hujson

repository·master·Indexed 21 days ago

https://github.com/tailscale/hujson

A Go implementation of the JWCC (JSON with Commas and Comments) format. HuJSON is a superset of standard JSON that supports C-style line and block comments as well as trailing commas. The library provides tools for parsing HuJSON into an AST, serializing data with Pack(), and modifying documents via RFC 6902 JSON Patch. It includes the hujsonfmt CLI for formatting, minifying, and standardizing HuJSON files to plain JSON.

Tokens
4.2K
Snippets
20
Records
25
Agent score
72%

What's inside hujson

  1. What is HuJSON (JWCC)?

    master

    HuJSON implements the JWCC (JSON with Commas and Comments) extension of standard JSON. It is a superset of standard JSON, meaning all valid JSON is also valid HuJSON.

    JWCC adds two key features to standard JSON:

    1. Comments: Supports both C-style line comments (//) and block comments (/* ... */) intermixed with whitespace.
    2. Trailing Commas: Allows trailing commas after the last member or element in an object or array.
  2. Associate *.hujson files with JSONC in Visual Studio Code

    master

    To get syntax highlighting and support for HuJSON in Visual Studio Code, you can associate *.hujson files with the jsonc (JSON with comments) language mode and explicitly enable trailing comma support in the JSON schema settings.

    Add the following snippet to your VS Code settings.json configuration:

    "files.associations": {
        "*.hujson": "jsonc"
    },
    "json.schemas": [{
        "fileMatch": ["*.hujson"],
        "schema": {
            "allowTrailingCommas": true
        }
    }]
  3. How comment preservation works during Patch operations

    master

    HuJSON attempts to preserve comments during patch operations, but because comment placement is subjective, certain assumptions are made:

    Preserved Comments:

    • Comments appearing before an object member name or an array element value are associated with that member/element and will move with it.
    • Comments appearing immediately after an object member value or an array element value are associated with that member/element and will move with it.

    Lost Comments: Comments in the following locations may be lost during patching:

    • Between an object member name and the colon.
    • Between the colon and the object member value.
    • Between a value and the following comma.

    Example of movement: If you move a path /name in a structure where comments are placed immediately before or after the value, those comments will follow the element to its new location.

  4. Parse HuJSON into an AST using Value

    master

    The hujson.Parse function (implied by package documentation) parses HuJSON input into a Value object. A Value is an exact syntactic representation (AST) of the input, preserving all comments and whitespace.

    Key characteristics of Value:

    • Preservation: Value.Pack serializes the tree back to bytes that are byte-for-byte identical to the input if no transformations were performed.
    • Mutation: Methods like Minimize, Standardize, Format, and Patch mutate the Value in place. To preserve the original, call v.Clone() first.
    • Traversal: Use the All() method to iterate through all values in the tree in depth-first order.
    // Example of iterating through all values in a HuJSON tree
    for v := range rootValue.All() {
        // do something with v
    }
  5. Understand HuJSON Value offsets and extra content

    master

    When using Parse, the returned Value provides metadata about the position of the value within the original byte slice:

    • StartOffset: The index in the input buffer where the value begins.
    • EndOffset: The index in the input buffer where the value ends.
    • BeforeExtra: A byte slice containing whitespace or comments appearing immediately before the value.
    • AfterExtra: A byte slice containing whitespace or comments appearing immediately after the value.

    This allows for round-tripping or preserving the "human" elements (comments/whitespace) of the original HuJSON source.

  6. Convert HuJSON to standard JSON for use with encoding/json

    master

    Because hujson operates on an AST, you cannot directly pass a Value to the standard library's json.Unmarshal. To parse HuJSON into arbitrary Go types, you must first convert the AST into standard JSON using hujson.Standardize.

    Workflow:

    1. Parse HuJSON into a hujson.Value.
    2. Call hujson.Standardize(value) to strip comments and trailing commas.
    3. Pass the resulting standard JSON bytes to json.Unmarshal.
    // Convert HuJSON to standard JSON for standard library compatibility
    b, err := hujson.Standardize(value)
    if err != nil {
        // handle err
    }
    if err := json.Unmarshal(b, &v); err != nil {
        // handle err
    }
  7. Identify JSON value types with Kind

    master

    The Kind type represents the underlying JSON type of a value. It is a single byte corresponding to the first byte of the grammar for that value.

    Supported Kinds:

    • 'n': null
    • 'f': false
    • 't': true
    • `'
  8. Check if a HuJSON value is standard JSON

    master

    Use the IsStandard() method on a Value to determine if the HuJSON content is compliant with standard JSON (RFC 8259). A value is considered standard if it contains no comments and no trailing commas.

    // Assuming 'v' is a hujson.Value
    if v.IsStandard() {
        // The value is valid standard JSON
    }
  9. Standardize a HuJSON value while preserving offsets

    master

    The Standardize() method on a Value strips HuJSON-specific features (comments and trailing commas) to make the content compliant with standard JSON (RFC 8259).

    Unlike Minimize(), Standardize() replaces comments and trailing commas with a space character instead of removing them. This ensures that the original line numbers and byte offsets are preserved, which is useful for tools that rely on positional information.

    // Assuming 'v' is a *hujson.Value
    v.Standardize()
  10. Parse HuJSON with Parse()

    master

    Use Parse(b []byte) to convert a HuJSON-encoded byte slice into a Value.

    Key behaviors:

    • Error Reporting: If parsing fails, the error is wrapped with the specific line and column number where the error occurred (e.g., hujson: line 5, column 12: ...).
    • Memory Efficiency: The resulting Value may contain Extra and Literal values that alias the original input buffer b to avoid unnecessary allocations.
    • Strictness: The parser expects exactly one top-level value. If there is trailing data after the top-level value, Parse returns an error indicating an invalid character after top-level value.
    import "github.com/tailscale/tailscale/hujson"
    
    input := []byte(`{
      // A comment
      "key": "value" 
    }`) 
    
    value, err := hujson.Parse(input)
    if err != nil {
        // Handle error (includes line/column info)
        panic(err)
    }
    // Use value...
  11. Format HuJSON with opinionated heuristics

    master

    Use Format to apply opinionated formatting to HuJSON, similar to how go fmt works for Go code.

    Key behaviors:

    • Idempotency: Formatting already formatted HuJSON results in no changes.
    • Standard JSON Compatibility: If the input is standard JSON, the output remains standard JSON.
    • Error Handling: If an error is encountered, the original byte slice is returned along with the error.
    • Heuristics: It handles indentation, alignment of object values, and expansion of composite values (objects/arrays) based on line length and content.
    output, err := hujson.Format(inputBytes)
    if err != nil {
        // handle error
    }