Created
March 14, 2026 16:50
-
-
Save cb341/9ab4e27c8108c1496f909e31c85ef73d to your computer and use it in GitHub Desktop.
A simple script to analyse JSON schema
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env ruby | |
| # describe_json - Parses JSON files and displays a detailed, human-readable | |
| # breakdown of the schema including data types, field names, array sizes, | |
| # and sample values. Supports file arguments or stdin input. | |
| # author: claude-haiku | |
| require 'json' | |
| def show_help | |
| puts <<~HELP | |
| describe_json - Analyze and describe JSON file structure | |
| Usage: | |
| ruby describe_json.rb [FILE] | |
| cat FILE | ruby describe_json.rb | |
| ruby describe_json.rb < FILE | |
| Options: | |
| --help Show this help message | |
| Examples: | |
| ruby describe_json.rb data.json | |
| cat config.json | ruby describe_json.rb | |
| HELP | |
| exit 0 | |
| end | |
| def format_type(value) | |
| case value | |
| when String | |
| truncated = value.length > 40 ? "#{value[0..40]}..." : value | |
| "string[#{value.length}] = #{truncated.inspect}" | |
| when Integer | |
| "int = #{value}" | |
| when Float | |
| "float = #{value}" | |
| when TrueClass, FalseClass | |
| "bool = #{value}" | |
| when NilClass | |
| "null" | |
| else | |
| value.class.to_s.downcase | |
| end | |
| end | |
| def describe_value(value, indent = 0, add_comma = false) | |
| prefix = " " * indent | |
| inner = " " * (indent + 1) | |
| case value | |
| when Hash | |
| puts "{" | |
| keys = value.keys | |
| keys.each_with_index do |key, idx| | |
| is_last = idx == keys.length - 1 | |
| val = value[key] | |
| comma = is_last ? "" : "," | |
| case val | |
| when Hash, Array | |
| print "#{inner}#{key.inspect}: " | |
| describe_value(val, indent + 1, !is_last) | |
| else | |
| puts "#{inner}#{key.inspect}: #{format_type(val)}#{comma}" | |
| end | |
| end | |
| puts "#{prefix}}#{add_comma ? ',' : ''}" | |
| when Array | |
| if value.empty? | |
| puts "[]#{add_comma ? ',' : ''}" | |
| else | |
| first = value.first | |
| puts "[" | |
| case first | |
| when Hash | |
| puts "#{inner}{" | |
| first.each_with_index do |(k, v), idx| | |
| is_last = idx == first.keys.length - 1 | |
| comma = is_last ? "" : "," | |
| case v | |
| when Hash, Array | |
| print "#{inner} #{k.inspect}: " | |
| describe_value(v, indent + 2, !is_last) | |
| else | |
| puts "#{inner} #{k.inspect}: #{format_type(v)}#{comma}" | |
| end | |
| end | |
| puts "#{inner}}" | |
| else | |
| puts "#{inner}#{format_type(first)}" | |
| end | |
| if value.length > 1 | |
| all_same = value.all? { |item| item.class == first.class } | |
| if all_same && first.is_a?(Hash) | |
| same_keys = value.all? { |item| item.keys.sort == first.keys.sort } | |
| puts "#{inner}... #{value.length} items total" if same_keys | |
| end | |
| end | |
| puts "#{prefix}]#{add_comma ? ',' : ''}" | |
| end | |
| else | |
| puts format_type(value) + (add_comma ? ',' : '') | |
| end | |
| end | |
| def summarize_structure(value) | |
| case value | |
| when Hash | |
| keys = value.keys | |
| "{ #{keys.map { |k| "#{k}: #{summarize_structure(value[k])}" }.join(', ')} }" | |
| when Array | |
| if value.empty? | |
| "[]" | |
| else | |
| first_summary = summarize_structure(value.first) | |
| value.length == 1 ? "[#{first_summary}]" : "[#{first_summary} (#{value.length} items)]" | |
| end | |
| when String | |
| "string(#{value.length})" | |
| when Integer | |
| "int" | |
| when Float | |
| "float" | |
| when TrueClass, FalseClass | |
| "bool" | |
| when NilClass | |
| "null" | |
| else | |
| value.class.to_s.downcase | |
| end | |
| end | |
| show_help if ARGV.include?('--help') | |
| input = if ARGV.length > 0 | |
| filename = ARGV[0] | |
| unless File.exist?(filename) | |
| puts "Error: File not found - #{filename}" | |
| exit 1 | |
| end | |
| File.read(filename) | |
| else | |
| STDIN.read | |
| end | |
| begin | |
| data = JSON.parse(input) | |
| puts "=== JSON Structure Description ===" | |
| puts | |
| puts "Root Type: #{data.class}" | |
| puts | |
| case data | |
| when Array | |
| puts "Array with #{data.length} items" | |
| puts "\nFirst Item Structure:" | |
| describe_value(data.first, 0) | |
| if data.length > 1 | |
| puts | |
| all_same = data.all? { |item| item.class == data.first.class } | |
| if all_same && data.first.is_a?(Hash) | |
| same_keys = data.all? { |item| item.keys.sort == data.first.keys.sort } | |
| puts "✓ All #{data.length} items have consistent structure" if same_keys | |
| puts "⚠ Items have varying structures" unless same_keys | |
| end | |
| end | |
| when Hash | |
| puts "Structure:" | |
| describe_value(data, 0) | |
| else | |
| puts "Value: #{data.inspect}" | |
| end | |
| puts "\n=== Compact Schema ===" | |
| puts summarize_structure(data) | |
| rescue JSON::ParserError => e | |
| puts "Error: Invalid JSON - #{e.message}" | |
| exit 1 | |
| rescue StandardError => e | |
| puts "Error: #{e.message}" | |
| exit 1 | |
| end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment