JSON Pointer Faraday Middleware

Posted on 20 Oct 2014 by Eric Oestrich

At Ruby DCamp I spent some time writing a faraday middleware that automatically dereferences JSON pointers. It was a fun exercise in reading an RFC spec and writing something that implements it. I wanted to do this after some cool demonstrations I saw at RESTFest this year. One was hyper+json which uses JSON Pointers.

Below is what I came up with. It is also hosted on github. The github project also contains specs which better highlight what is happening.

This code will take the following JSON:

{
  "name": "/first_name",
  "first_name": "Eric",
}

And convert it into:

{
  "name": "Eric",
  "first_name": "Eric",
}

It even allows deep linking of attributes.

json_faraday_middleware.rb
require 'faraday'
require 'json'

class JsonPointerMiddleware < Faraday::Middleware
  JSON_MIME_TYPE = "application/json"

  def call(env)
    @app.call(env).on_complete do |env|
      unless env.response_headers["Content-Type"] == JSON_MIME_TYPE
        return
      end

      @body = JSON.parse(env.body)
      pointerize(@body)
      begin
        env.body = @body.to_json
      rescue JSON::NestingError
        # there is a circular nest, skip dereferencing
      end
    end
  end

  def pointerize(body)
    body.each do |key, value|
      if value.is_a?(Hash)
        pointerize(value)
      end
      next unless value =~ /\//

      pointer_keys = value.split("/")[1..-1]
      pointer_keys = escape_slash(pointer_keys)
      pointer_keys = escape_tilde(pointer_keys)
      pointer_keys = convert_indices(pointer_keys)
      new_value = pointer_keys.inject(@body) do |body, pointer_key|
        next if body.nil?
        body[pointer_key]
      end
      body[key] = new_value if new_value
    end
  end

  def escape_slash(keys)
    keys.map do |key|
      key.gsub("~1", "/")
    end
  end

  def escape_tilde(keys)
    keys.map do |key|
      key.gsub("~0", "~")
    end
  end

  # Convert array indices to Integers
  def convert_indices(keys)
    keys.map do |key|
      Integer(key) rescue key
    end
  end
end
comments powered by Disqus
Creative Commons License
This site's content is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License unless otherwise specified. Code on this site is licensed under the MIT License unless otherwise specified.