ruby 38 lines · 5 steps

Parsing fixed-width records in Ruby

A data-driven parser turns fixed-width text lines into typed hashes using a declarative field table.

Explained by highlit
1module FixedWidthParser
2 FIELDS = [
3 { name: :record_type, range: 0...2 },
4 { name: :account_id, range: 2...12, type: :integer },
5 { name: :last_name, range: 12...42, type: :string },
6 { name: :first_name, range: 42...62, type: :string },
7 { name: :balance, range: 62...74, type: :cents },
8 { name: :opened_on, range: 74...82, type: :date },
9 { name: :active, range: 82...83, type: :flag }
10 ].freeze
11 
12 module_function
13 
14 def parse(io)
15 io.each_line.filter_map do |line|
16 next if line.strip.empty?
17 parse_line(line.chomp)
18 end
19 end
20 
21 def parse_line(line)
22 FIELDS.each_with_object({}) do |field, record|
23 raw = line[field[:range]].to_s
24 record[field[:name]] = cast(raw, field[:type])
25 end
26 end
27 
28 def cast(raw, type)
29 case type
30 when :integer then Integer(raw.strip, 10)
31 when :string then raw.strip
32 when :cents then Integer(raw.strip, 10) / 100.0
33 when :date then Date.strptime(raw.strip, "%Y%m%d")
34 when :flag then raw.strip == "Y"
35 else raw
36 end
37 end
38end
01 / 01
STEP 01

Walkthrough

Space play step click any line
Three takeaways
  1. 1Describing structure as data lets one small engine handle every field without branching per column.
  2. 2Ruby ranges over strings slice fixed-width columns cleanly, and filter_map drops blanks in one pass.
  3. 3Centralizing type coercion in a case statement keeps parsing logic in one place and easy to extend.

Related explainers

Share this explainer

Here's the card — post it anywhere.

Parsing fixed-width records in Ruby — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code