Psych YAML Parser and Emitter for Ruby

repository·master·Indexed 20 days ago

https://github.com/ruby/psych

Psych is a YAML parser and emitter for Ruby that leverages libyaml for core capabilities, providing serialization and deserialization of Ruby objects. It includes high-level methods like Psych.safe_load and Psych.dump, mid-level AST parsing, and low-level event-based APIs. Psych also features an experimental libfyaml backend for YAML 1.2 compliance and supports custom serialization via Psych::Coder and custom handlers inheriting from Psych::Handler.

Tokens
6K
Snippets
27
Records
31
Agent score
69%

What's inside Psych

  1. How the experimental libfyaml backend works

    master

    Psych features an experimental, opt-in backend built on libfyaml. This backend targets YAML 1.2 compliance, whereas the default libyaml backend follows YAML 1.1.

    Key Differences

    • YAML 1.2 Semantics: In libfyaml, YAML 1.1 booleans like yes, no, on, and off are loaded as plain strings instead of true/false. This resolves the "Norway problem" where the country code no was parsed as false.
    • Performance: libfyaml is competitive with libyaml for parsing but is significantly slower at emitting (roughly 1.7x to 1.9x slower).
    • Error Handling: On a parse error, Psych::SyntaxError#problem contains the full libfyaml diagnostic message, and Psych::SyntaxError#context is always nil.
    • Output Formatting: Byte-for-byte output parity with libyaml is not guaranteed.

    When to use it

    • Use libfyaml when you require strict YAML 1.2 semantics.
    • Use the default libyaml backend when throughput and performance are your priority.

    Checking the active backend

    Use Psych::BACKEND to see which backend is active and Psych.libfyaml_version to check the version.

    Psych::BACKEND         # => "libfyaml" (or "libyaml")
    Psych.libfyaml_version # => "0.9.6"
    
    # Example of YAML 1.2 boolean handling:
    Psych.load("country: no") # => {"country" => "no"}
  2. Build Psych with a specific libyaml source

    master

    If you need to build Psych using a specific version of the libyaml source files, you can pass the source directory during installation.

    gem install psych -- --with-libyaml-source-dir=/path/to/libyaml-0.2.5
  3. Install Psych via RubyGems

    master

    Psych is included with MRI Ruby (since 1.9.2). To use a newer gem release instead of the standard library version, install it via RubyGems.

    To use the gem release in your application rather than the stdlib version, ensure you include gem 'psych' in your Gemfile or explicitly require 'psych' in your code.

    gem install psych
  4. Enable the experimental libfyaml backend

    master

    To use the libfyaml backend, you must first install libfyaml and pkg-config on your system. Note that this backend is not supported on Windows.

    Installation steps (Linux/macOS):

    1. Install system dependencies:

      • Debian/Ubuntu: apt-get install libfyaml-dev
      • macOS: brew install libfyaml
    2. Install the gem with the enable flag: gem install psych -- --enable-libfyaml

    # Debian/Ubuntu
    apt-get install libfyaml-dev
    
    # macOS
    brew install libfyaml
    
    # Install Psych with libfyaml enabled
    gem install psych -- --enable-libfyaml
  5. Custom YAML serialization with Psych::Coder

    master

    When an object defines an encode_with method, Psych passes an instance of Psych::Coder to it during serialization. The Coder object allows you to control how your object is represented in the resulting YAML by specifying its tag, style, and data structure (Mapping, Sequence, or Scalar).

    By default, the Coder assumes a Psych::Nodes::Mapping is being emitted. You can change this behavior by calling specific methods to represent the object as a scalar, a sequence, or an arbitrary object.

    class MyCustomClass
      def encode_with(coder)
        coder.tag = '!my_tag'
        coder['key'] = 'value'
        coder['list'] = [1, 2, 3]
      end
    end
  6. Implement a custom Psych::Handler for YAML parsing

    master

    To process YAML events manually using Psych::Parser, you must create a class that inherits from Psych::Handler. Psych::Handler is an abstract base class that defines all possible events a parser can emit. You should override the specific event methods (such as scalar, start_mapping, or alias) that you want to handle during the parsing process.

    class MyHandler < Psych::Handler
      def scalar(value, anchor, tag, plain, quoted, style)
        puts "Found scalar: #{value}"
      end
    
      def start_mapping(anchor, tag, implicit, style)
        puts "Starting a map"
      end
    end
  7. Low-level Event-based Parsing and Emitting

    master

    For maximum performance or when the document format is known in advance, use the low-level event-based API. This avoids the overhead of building an AST or converting to Ruby objects.

    Parsing Events

    Use Psych::Parser with a handler. A handler can be a Psych::TreeBuilder (to build an AST) or a custom handler (to react to events).

    # Using a Recorder to capture events
    recorder = Psych::Handlers::Recorder.new
    parser = Psych::Parser.new(recorder)
    parser.parse("--- a\n - b")
    # recorder.events contains the list of events and arguments

    Emitting Events

    Use Psych::Emitter to convert a stream of events into a YAML document.

  8. Safely load and emit YAML with Psych

    master

    Psych provides methods to parse YAML strings into Ruby objects and to serialize Ruby objects into YAML strings.

    • Use Psych.safe_load(yaml_string) to parse YAML safely into a Ruby object.
    • Use Psych.dump(object) to emit a Ruby object as a YAML string.
    # Safely load YAML in to a Ruby object
    Psych.safe_load('--- foo') # => 'foo'
    
    # Emit YAML from a Ruby object
    Psych.dump("foo")     # => "--- foo\n...\n"
  9. Handle disallowed classes with DisallowedClass

    master

    When using Psych.safe_load, Psych restricts the types of Ruby objects that can be deserialized to prevent security vulnerabilities. If the YAML document contains a class that is not explicitly permitted, Psych raises Psych::DisallowedClass.

    To fix this, you must include the required class in the permitted list when calling safe_load.

    # Example of handling DisallowedClass
    begin
      Psych.safe_load(yaml_string)
    rescue Psych::DisallowedClass => e
      # e.message will indicate which class was disallowed
      puts e.message
    end
  10. Handle YAML alias errors with AliasesNotEnabled and AnchorNotDefined

    master

    When parsing YAML that contains aliases, Psych may raise specific exceptions if aliases are not permitted or if a referenced anchor is missing.

    • Psych::AliasesNotEnabled: Raised when an alias is encountered but alias parsing is disabled. To resolve this, pass aliases: true to Psych.load or Psych.safe_load.
    • Psych::AnchorNotDefined: Raised when an alias refers to an anchor name that was not previously defined in the document.
    # Example of resolving AliasesNotEnabled
    begin
      Psych.safe_load(yaml_string)
    rescue Psych::AliasesNotEnabled
      # Enable aliases to fix the error
      Psych.safe_load(yaml_string, aliases: true)
    end
  11. Handle YAML syntax errors

    master

    When parsing invalid YAML, Psych raises a Psych::SyntaxError. You can catch this exception to access details about where the error occurred.

    If you provide a filename argument to Psych.parse, Psych.load, or Psych.safe_load, that filename will be included in the exception message.

    begin
      Psych.parse("--- `", filename: "file.txt")
    rescue Psych::SyntaxError => ex
      puts ex.file    # => 'file.txt'
      puts ex.message # => "(file.txt): found character that cannot start any token"
    end
  12. Catch general Psych exceptions

    master

    All Psych-specific errors inherit from Psych::Exception. You can use this class to catch any error related to YAML parsing, loading, or emitting within the Psych library.

    begin
      Psych.load(invalid_yaml)
    rescue Psych::Exception => e
      puts "A Psych error occurred: #{e.message}"
    end