boehnenelton2024

BEJSON 104 Bash Library

This is a comprehensive Bash library for creating, validating, and interacting with BEJSON 104 files. It provides a rich set of functions for file creation, record management, data retrieval, and advanced queries, all from the command line using `jq`.

Full Library Source


#!/bin/bash

################################################################################
###                      BEJSON 104 BASH LIBRARY                            ###
###                                                                          ###
### A comprehensive bash library for working with BEJSON version 104 files  ###
### Created for handling structured data interchange in bash scripts        ###
###                                                                          ###
### Author: BEJSON 104 Library Team                                         ###
### Version: 1.0.0                                                          ###
### License: MIT                                                            ###
################################################################################

# Global variables for library state
declare -g BEJSON_LOADED_FILE=""
declare -g BEJSON_LOADED_CONTENT=""
declare -g BEJSON_IS_VALID=0

################################################################################
###                         UTILITY FUNCTIONS                               ###
################################################################################

### FUNCTION: bejson_check_dependencies ###
# Description: Checks if required dependencies (jq) are installed
# Parameters: None
# Returns: 0 if dependencies are met, 1 otherwise
bejson_check_dependencies() {
    if ! command -v jq &> /dev/null; then
        echo "Error: jq is required but not installed. Please install jq to use this library." >&2
        return 1
    fi
    return 0
}

### FUNCTION: bejson_error ###
# Description: Prints an error message to stderr
# Parameters: $1 - Error message
# Returns: None
bejson_error() {
    echo "BEJSON Error: $1" >&2
}

### FUNCTION: bejson_success ###
# Description: Prints a success message to stdout
# Parameters: $1 - Success message
# Returns: None
bejson_success() {
    echo "BEJSON Success: $1"
}

################################################################################
###                    FILE CREATION FUNCTIONS                              ###
################################################################################

### FUNCTION: bejson_create_file ###
# Description: Creates a new BEJSON 104 file with the specified structure
# Parameters: 
#   $1 - Output file path
#   $2 - Records type (e.g., "Book", "User", etc.)
#   $3 - Fields JSON array (e.g., '[{"name":"ID","type":"integer"},{"name":"Name","type":"string"}]')
#   $4 - (Optional) Format creator name (defaults to "Elton Boehnen")
#   $5 - (Optional) Parent hierarchy
# Returns: 0 on success, 1 on failure
# Example: bejson_create_file "data.json" "Book" '[{"name":"ISBN","type":"string"},{"name":"Title","type":"string"}]'
bejson_create_file() {
    local output_file="$1"
    local records_type="$2"
    local fields="$3"
    local format_creator="${4:-Elton Boehnen}"
    local parent_hierarchy="$5"
    
    # Validate parameters
    if [[ -z "$output_file" || -z "$records_type" || -z "$fields" ]]; then
        bejson_error "Missing required parameters for bejson_create_file"
        echo "Usage: bejson_create_file    [format_creator] [parent_hierarchy]"
        return 1
    fi
    
    # Validate fields JSON
    if ! echo "$fields" | jq empty 2>/dev/null; then
        bejson_error "Invalid JSON format for fields parameter"
        return 1
    fi
    
    # Build the BEJSON structure
    local bejson_content
    if [[ -n "$parent_hierarchy" ]]; then
        bejson_content=$(jq -n             --arg format "BEJSON"             --arg version "104"             --arg creator "$format_creator"             --arg parent "$parent_hierarchy"             --arg rectype "$records_type"             --argjson fields "$fields"             '{
                "Format": $format,
                "Format_Version": $version,
                "Format_Creator": $creator,
                "Parent_Hierarchy": $parent,
                "Records_Type": [$rectype],
                "Fields": $fields,
                "Values": []
            }')
    else
        bejson_content=$(jq -n             --arg format "BEJSON"             --arg version "104"             --arg creator "$format_creator"             --arg rectype "$records_type"             --argjson fields "$fields"             '{
                "Format": $format,
                "Format_Version": $version,
                "Format_Creator": $creator,
                "Records_Type": [$rectype],
                "Fields": $fields,
                "Values": []
            }')
    fi
    
    # Write to file
    echo "$bejson_content" > "$output_file"
    
    if [[ $? -eq 0 ]]; then
        bejson_success "BEJSON 104 file created: $output_file"
        return 0
    else
        bejson_error "Failed to write BEJSON file: $output_file"
        return 1
    fi
}

### FUNCTION: bejson_add_record ###
# Description: Adds a record (row of values) to an existing BEJSON 104 file
# Parameters:
#   $1 - BEJSON file path
#   $2 - Values JSON array (e.g., '["978-0321765723", "The C++ Programming Language", 2013]')
# Returns: 0 on success, 1 on failure
# Example: bejson_add_record "data.json" '["978-0321765723", "The C++ Programming Language", 2013]'
bejson_add_record() {
    local file_path="$1"
    local values="$2"
    
    # Validate parameters
    if [[ -z "$file_path" || -z "$values" ]]; then
        bejson_error "Missing required parameters for bejson_add_record"
        echo "Usage: bejson_add_record  "
        return 1
    fi
    
    # Check if file exists
    if [[ ! -f "$file_path" ]]; then
        bejson_error "File not found: $file_path"
        return 1
    fi
    
    # Validate values JSON
    if ! echo "$values" | jq empty 2>/dev/null; then
        bejson_error "Invalid JSON format for values parameter"
        return 1
    fi
    
    # Add the record to the Values array
    local updated_content
    updated_content=$(jq --argjson newval "$values" '.Values += [$newval]' "$file_path")
    
    if [[ $? -eq 0 ]]; then
        echo "$updated_content" > "$file_path"
        bejson_success "Record added to $file_path"
        return 0
    else
        bejson_error "Failed to add record to $file_path"
        return 1
    fi
}

################################################################################
###                    FILE LOADING FUNCTIONS                               ###
################################################################################

### FUNCTION: bejson_load_file ###
# Description: Loads a BEJSON 104 file into memory for processing
# Parameters: $1 - File path to load
# Returns: 0 on success, 1 on failure
# Side Effects: Sets BEJSON_LOADED_FILE, BEJSON_LOADED_CONTENT, BEJSON_IS_VALID
# Example: bejson_load_file "data.json"
bejson_load_file() {
    local file_path="$1"
    
    # Validate parameter
    if [[ -z "$file_path" ]]; then
        bejson_error "Missing file path parameter for bejson_load_file"
        echo "Usage: bejson_load_file "
        return 1
    fi
    
    # Check if file exists
    if [[ ! -f "$file_path" ]]; then
        bejson_error "File not found: $file_path"
        return 1
    fi
    
    # Check if file is readable
    if [[ ! -r "$file_path" ]]; then
        bejson_error "File is not readable: $file_path"
        return 1
    fi
    
    # Load file content
    BEJSON_LOADED_CONTENT=$(cat "$file_path")
    
    # Validate JSON format
    if ! echo "$BEJSON_LOADED_CONTENT" | jq empty 2>/dev/null; then
        bejson_error "File is not valid JSON: $file_path"
        BEJSON_LOADED_FILE=""
        BEJSON_LOADED_CONTENT=""
        BEJSON_IS_VALID=0
        return 1
    fi
    
    BEJSON_LOADED_FILE="$file_path"
    BEJSON_IS_VALID=1
    
    bejson_success "File loaded: $file_path"
    return 0
}

### FUNCTION: bejson_unload_file ###
# Description: Clears the currently loaded BEJSON file from memory
# Parameters: None
# Returns: 0 always
# Example: bejson_unload_file
bejson_unload_file() {
    BEJSON_LOADED_FILE=""
    BEJSON_LOADED_CONTENT=""
    BEJSON_IS_VALID=0
    bejson_success "File unloaded from memory"
    return 0
}

### FUNCTION: bejson_get_loaded_file ###
# Description: Returns the path of the currently loaded file
# Parameters: None
# Returns: Prints the file path, or empty string if no file is loaded
# Example: current_file=$(bejson_get_loaded_file)
bejson_get_loaded_file() {
    echo "$BEJSON_LOADED_FILE"
}

################################################################################
###                    VALIDATION FUNCTIONS                                 ###
################################################################################

### FUNCTION: bejson_check_format_version ###
# Description: Checks if a file or loaded content has the correct BEJSON 104 format version
# Parameters: $1 - (Optional) File path. If not provided, uses loaded content
# Returns: 0 if version is "104", 1 otherwise
# Example: bejson_check_format_version "data.json"
bejson_check_format_version() {
    local file_path="$1"
    local content
    
    if [[ -n "$file_path" ]]; then
        # Check specific file
        if [[ ! -f "$file_path" ]]; then
            bejson_error "File not found: $file_path"
            return 1
        fi
        content=$(cat "$file_path")
    else
        # Use loaded content
        if [[ -z "$BEJSON_LOADED_CONTENT" ]]; then
            bejson_error "No file loaded. Use bejson_load_file first or provide a file path"
            return 1
        fi
        content="$BEJSON_LOADED_CONTENT"
    fi
    
    local format_version
    format_version=$(echo "$content" | jq -r '.Format_Version // empty')
    
    if [[ "$format_version" == "104" ]]; then
        bejson_success "Format version is 104"
        return 0
    else
        bejson_error "Format version is not 104 (found: $format_version)"
        return 1
    fi
}

### FUNCTION: bejson_validate_file ###
# Description: Validates a BEJSON 104 file against the specification
# Parameters: $1 - (Optional) File path. If not provided, uses loaded content
# Returns: 0 if valid, 1 if invalid
# Example: bejson_validate_file "data.json"
bejson_validate_file() {
    local file_path="$1"
    local content
    
    if [[ -n "$file_path" ]]; then
        # Validate specific file
        if [[ ! -f "$file_path" ]]; then
            bejson_error "File not found: $file_path"
            return 1
        fi
        content=$(cat "$file_path")
    else
        # Use loaded content
        if [[ -z "$BEJSON_LOADED_CONTENT" ]]; then
            bejson_error "No file loaded. Use bejson_load_file first or provide a file path"
            return 1
        fi
        content="$BEJSON_LOADED_CONTENT"
    fi
    
    # Validate JSON format
    if ! echo "$content" | jq empty 2>/dev/null; then
        bejson_error "Invalid JSON format"
        return 1
    fi
    
    # Check mandatory fields
    local format=$(echo "$content" | jq -r '.Format // empty')
    local format_version=$(echo "$content" | jq -r '.Format_Version // empty')
    local records_type=$(echo "$content" | jq -r '.Records_Type // empty')
    local fields=$(echo "$content" | jq -r '.Fields // empty')
    local values=$(echo "$content" | jq -r '.Values // empty')
    
    # Validate Format
    if [[ "$format" != "BEJSON" ]]; then
        bejson_error "Invalid or missing Format field (must be 'BEJSON')"
        return 1
    fi
    
    # Validate Format_Version
    if [[ "$format_version" != "104" ]]; then
        bejson_error "Invalid or missing Format_Version field (must be '104')"
        return 1
    fi
    
    # Validate Records_Type (must be array with exactly one element)
    local records_type_count=$(echo "$content" | jq '.Records_Type | length')
    if [[ "$records_type_count" != "1" ]]; then
        bejson_error "Records_Type must contain exactly one element (found: $records_type_count)"
        return 1
    fi
    
    # Validate Fields (must be array)
    if [[ "$fields" == "null" ]] || [[ -z "$fields" ]]; then
        bejson_error "Missing Fields array"
        return 1
    fi
    
    local fields_is_array=$(echo "$content" | jq -r '.Fields | type')
    if [[ "$fields_is_array" != "array" ]]; then
        bejson_error "Fields must be an array"
        return 1
    fi
    
    # Validate each field has name and type
    local fields_count=$(echo "$content" | jq '.Fields | length')
    for ((i=0; i [file_path]"
        return 1
    fi
    
    if [[ -n "$file_path" ]]; then
        if [[ ! -f "$file_path" ]]; then
            bejson_error "File not found: $file_path"
            return 1
        fi
        content=$(cat "$file_path")
    else
        if [[ -z "$BEJSON_LOADED_CONTENT" ]]; then
            bejson_error "No file loaded. Use bejson_load_file first or provide a file path"
            return 1
        fi
        content="$BEJSON_LOADED_CONTENT"
    fi
    
    # Check if index is valid
    local total_records=$(echo "$content" | jq '.Values | length')
    if [[ "$index" -ge "$total_records" ]] || [[ "$index" -lt 0 ]]; then
        bejson_error "Invalid record index: $index (total records: $total_records)"
        return 1
    fi
    
    echo "$content" | jq -c ".Values[$index]"
}

### FUNCTION: bejson_get_multiple_records ###
# Description: Retrieves multiple records by index range (0-based, inclusive)
# Parameters:
#   $1 - Start index (0-based)
#   $2 - End index (0-based, inclusive)
#   $3 - (Optional) File path. If not provided, uses loaded content
# Returns: Prints the records as JSON array
# Example: records=$(bejson_get_multiple_records 0 2 "data.json")
bejson_get_multiple_records() {
    local start_index="$1"
    local end_index="$2"
    local file_path="$3"
    local content
    
    if [[ -z "$start_index" ]] || [[ -z "$end_index" ]]; then
        bejson_error "Missing index parameters"
        echo "Usage: bejson_get_multiple_records   [file_path]"
        return 1
    fi
    
    if [[ -n "$file_path" ]]; then
        if [[ ! -f "$file_path" ]]; then
            bejson_error "File not found: $file_path"
            return 1
        fi
        content=$(cat "$file_path")
    else
        if [[ -z "$BEJSON_LOADED_CONTENT" ]]; then
            bejson_error "No file loaded. Use bejson_load_file first or provide a file path"
            return 1
        fi
        content="$BEJSON_LOADED_CONTENT"
    fi
    
    # Validate indices
    local total_records=$(echo "$content" | jq '.Values | length')
    if [[ "$start_index" -lt 0 ]] || [[ "$end_index" -ge "$total_records" ]] || [[ "$start_index" -gt "$end_index" ]]; then
        bejson_error "Invalid index range: $start_index to $end_index (total records: $total_records)"
        return 1
    fi
    
    echo "$content" | jq -c ".Values[$start_index:$((end_index + 1))]"
}

### FUNCTION: bejson_get_total_records ###
# Description: Returns the total number of records in the Values array
# Parameters: $1 - (Optional) File path. If not provided, uses loaded content
# Returns: Prints the count as integer
# Example: count=$(bejson_get_total_records "data.json")
bejson_get_total_records() {
    local file_path="$1"
    local content
    
    if [[ -n "$file_path" ]]; then
        if [[ ! -f "$file_path" ]]; then
            bejson_error "File not found: $file_path"
            return 1
        fi
        content=$(cat "$file_path")
    else
        if [[ -z "$BEJSON_LOADED_CONTENT" ]]; then
            bejson_error "No file loaded. Use bejson_load_file first or provide a file path"
            return 1
        fi
        content="$BEJSON_LOADED_CONTENT"
    fi
    
    echo "$content" | jq '.Values | length'
}

### FUNCTION: bejson_get_record_value_by_field ###
# Description: Retrieves a specific field value from a specific record
# Parameters:
#   $1 - Record index (0-based)
#   $2 - Field name
#   $3 - (Optional) File path. If not provided, uses loaded content
# Returns: Prints the value
# Example: value=$(bejson_get_record_value_by_field 0 "Title" "data.json")
bejson_get_record_value_by_field() {
    local record_index="$1"
    local field_name="$2"
    local file_path="$3"
    local content
    
    if [[ -z "$record_index" ]] || [[ -z "$field_name" ]]; then
        bejson_error "Missing required parameters"
        echo "Usage: bejson_get_record_value_by_field   [file_path]"
        return 1
    fi
    
    if [[ -n "$file_path" ]]; then
        if [[ ! -f "$file_path" ]]; then
            bejson_error "File not found: $file_path"
            return 1
        fi
        content=$(cat "$file_path")
    else
        if [[ -z "$BEJSON_LOADED_CONTENT" ]]; then
            bejson_error "No file loaded. Use bejson_load_file first or provide a file path"
            return 1
        fi
        content="$BEJSON_LOADED_CONTENT"
    fi
    
    # Find field index
    local field_index=$(echo "$content" | jq --arg fname "$field_name" '[.Fields[].name] | index($fname)')
    
    if [[ "$field_index" == "null" ]]; then
        bejson_error "Field not found: $field_name"
        return 1
    fi
    
    # Validate record index
    local total_records=$(echo "$content" | jq '.Values | length')
    if [[ "$record_index" -ge "$total_records" ]] || [[ "$record_index" -lt 0 ]]; then
        bejson_error "Invalid record index: $record_index (total records: $total_records)"
        return 1
    fi
    
    # Get the value
    echo "$content" | jq -r ".Values[$record_index][$field_index]"
}

### FUNCTION: bejson_get_multiple_record_values_by_field ###
# Description: Retrieves a specific field value from multiple records
# Parameters:
#   $1 - Field name
#   $2 - (Optional) File path. If not provided, uses loaded content
# Returns: Prints the values as JSON array
# Example: values=$(bejson_get_multiple_record_values_by_field "Title" "data.json")
bejson_get_multiple_record_values_by_field() {
    local field_name="$1"
    local file_path="$2"
    local content
    
    if [[ -z "$field_name" ]]; then
        bejson_error "Missing field name parameter"
        echo "Usage: bejson_get_multiple_record_values_by_field  [file_path]"
        return 1
    fi
    
    if [[ -n "$file_path" ]]; then
        if [[ ! -f "$file_path" ]]; then
            bejson_error "File not found: $file_path"
            return 1
        fi
        content=$(cat "$file_path")
    else
        if [[ -z "$BEJSON_LOADED_CONTENT" ]]; then
            bejson_error "No file loaded. Use bejson_load_file first or provide a file path"
            return 1
        fi
        content="$BEJSON_LOADED_CONTENT"
    fi
    
    # Find field index
    local field_index=$(echo "$content" | jq --arg fname "$field_name" '[.Fields[].name] | index($fname)')
    
    if [[ "$field_index" == "null" ]]; then
        bejson_error "Field not found: $field_name"
        return 1
    fi
    
    # Get all values for this field
    echo "$content" | jq -c "[.Values[][$field_index]]"
}

### FUNCTION: bejson_get_records_type ###
# Description: Retrieves the Records_Type from a BEJSON file
# Parameters: $1 - (Optional) File path. If not provided, uses loaded content
# Returns: Prints the record type as string
# Example: rec_type=$(bejson_get_records_type "data.json")
bejson_get_records_type() {
    local file_path="$1"
    local content
    
    if [[ -n "$file_path" ]]; then
        if [[ ! -f "$file_path" ]]; then
            bejson_error "File not found: $file_path"
            return 1
        fi
        content=$(cat "$file_path")
    else
        if [[ -z "$BEJSON_LOADED_CONTENT" ]]; then
            bejson_error "No file loaded. Use bejson_load_file first or provide a file path"
            return 1
        fi
        content="$BEJSON_LOADED_CONTENT"
    fi
    
    echo "$content" | jq -r '.Records_Type[0]'
}

################################################################################
###                    ADVANCED QUERY FUNCTIONS                             ###
################################################################################

### FUNCTION: bejson_filter_records_by_field ###
# Description: Filters records where a specific field matches a value
# Parameters:
#   $1 - Field name
#   $2 - Value to match
#   $3 - (Optional) File path. If not provided, uses loaded content
# Returns: Prints matching records as JSON array
# Example: matches=$(bejson_filter_records_by_field "Author" "Robert C. Martin" "data.json")
bejson_filter_records_by_field() {
    local field_name="$1"
    local match_value="$2"
    local file_path="$3"
    local content
    
    if [[ -z "$field_name" ]] || [[ -z "$match_value" ]]; then
        bejson_error "Missing required parameters"
        echo "Usage: bejson_filter_records_by_field   [file_path]"
        return 1
    fi
    
    if [[ -n "$file_path" ]]; then
        if [[ ! -f "$file_path" ]]; then
            bejson_error "File not found: $file_path"
            return 1
        fi
        content=$(cat "$file_path")
    else
        if [[ -z "$BEJSON_LOADED_CONTENT" ]]; then
            bejson_error "No file loaded. Use bejson_load_file first or provide a file path"
            return 1
        fi
        content="$BEJSON_LOADED_CONTENT"
    fi
    
    # Find field index
    local field_index=$(echo "$content" | jq --arg fname "$field_name" '[.Fields[].name] | index($fname)')
    
    if [[ "$field_index" == "null" ]]; then
        bejson_error "Field not found: $field_name"
        return 1
    fi
    
    # Get field type
    local field_type=$(echo "$content" | jq -r ".Fields[$field_index].type")
    
    # Filter records based on type
    if [[ "$field_type" == "integer" ]] || [[ "$field_type" == "float" ]]; then
        # Numeric comparison
        echo "$content" | jq -c --argjson val "$match_value" --argjson idx "$field_index" '[.Values[] | select(.[$idx] == $val)]'
    elif [[ "$field_type" == "boolean" ]]; then
        # Boolean comparison
        local bool_val="false"
        if [[ "$match_value" == "true" ]] || [[ "$match_value" == "1" ]]; then
            bool_val="true"
        fi
        echo "$content" | jq -c --argjson val "$bool_val" --argjson idx "$field_index" '[.Values[] | select(.[$idx] == $val)]'
    else
        # String comparison
        echo "$content" | jq -c --arg val "$match_value" --argjson idx "$field_index" '[.Values[] | select(.[$idx] == $val)]'
    fi
}

### FUNCTION: bejson_print_table ###
# Description: Prints records in a human-readable table format
# Parameters: $1 - (Optional) File path. If not provided, uses loaded content
# Returns: Prints formatted table to stdout
# Example: bejson_print_table "data.json"
bejson_print_table() {
    local file_path="$1"
    local content
    
    if [[ -n "$file_path" ]]; then
        if [[ ! -f "$file_path" ]]; then
            bejson_error "File not found: $file_path"
            return 1
        fi
        content=$(cat "$file_path")
    else
        if [[ -z "$BEJSON_LOADED_CONTENT" ]]; then
            bejson_error "No file loaded. Use bejson_load_file first or provide a file path"
            return 1
        fi
        content="$BEJSON_LOADED_CONTENT"
    fi
    
    # Get field names
    local field_names=$(echo "$content" | jq -r '.Fields[].name' | tr '
' '	')
    echo -e "$field_names"
    echo "----------------------------------------"
    
    # Print each record
    local total_records=$(echo "$content" | jq '.Values | length')
    for ((i=0; i/dev/null; then
        bejson_error "Content is not valid JSON."
        return 1
    fi
    
    # Assign to the specified variable in the caller's scope
    eval "$_output_var='$_temp_content'"
    return 0
}

# Internal helper to write updated content back to file or global variable.
# Parameters: $1 - file_path (optional). If empty, updates BEJSON_LOADED_CONTENT.
#             $2 - new_content (JSON string)
# Returns: 0 on success, 1 on failure.
_bejson_write_content_back() {
    local _file_path="$1"
    local _new_content="$2"

    if [[ -n "$_file_path" ]]; then
        echo "$_new_content" > "$_file_path"
        if [[ $? -ne 0 ]]; then
            bejson_error "Failed to write updated content to $_file_path"
            return 1
        fi
        # If the file was the loaded one, update global content as well
        if [[ "$BEJSON_LOADED_FILE" == "$_file_path" ]]; then
            BEJSON_LOADED_CONTENT="$_new_content"
        fi
    else
        BEJSON_LOADED_CONTENT="$_new_content"
    fi
    return 0
}

################################################################################
###                    DATA MODIFICATION FUNCTIONS                          ###
################################################################################

### FUNCTION: bejson_set_record ###
# Description: Replaces an entire record (row of values) at a given index.
# Parameters:
#   $1 - Record index (0-based)
#   $2 - New values JSON array (e.g., '["new_isbn", "New Title", 2024]')
#   $3 - (Optional) File path. If not provided, uses loaded content.
# Returns: 0 on success, 1 on failure.
# Example: bejson_set_record 0 '["978-1234567890", "Updated Book", 2024]' "data.json"
bejson_set_record() {
    local record_index="$1"
    local new_values_json="$2"
    local file_path="$3"
    local content
    
    if [[ -z "$record_index" || -z "$new_values_json" ]]; then
        bejson_error "Missing required parameters for bejson_set_record"
        echo "Usage: bejson_set_record   [file_path]"
        return 1
    fi
    
    if ! _bejson_get_content_or_load "$file_path" content; then return 1; fi
    
    # Validate new_values_json
    if ! echo "$new_values_json" | jq empty 2>/dev/null; then
        bejson_error "Invalid JSON format for new_values_json parameter"
        return 1
    fi
    
    local total_records=$(echo "$content" | jq '.Values | length')
    if [[ "$record_index" -ge "$total_records" ]] || [[ "$record_index" -lt 0 ]]; then
        bejson_error "Invalid record index: $record_index (total records: $total_records)"
        return 1
    fi

    local fields_count=$(echo "$content" | jq '.Fields | length')
    local new_values_count=$(echo "$new_values_json" | jq 'length')
    if [[ "$new_values_count" -ne "$fields_count" ]]; then
        bejson_error "New values array has $new_values_count elements, but $fields_count fields are defined."
        return 1
    fi
    
    local updated_content
    updated_content=$(echo "$content" | jq --argjson newval "$new_values_json" --argjson idx "$record_index" '.Values[$idx] = $newval')
    
    if [[ $? -eq 0 ]]; then
        if ! _bejson_write_content_back "$file_path" "$updated_content"; then return 1; fi
        bejson_success "Record $record_index updated."
        return 0
    else
        bejson_error "Failed to set record $record_index."
        return 1
    fi
}

### FUNCTION: bejson_set_value ###
# Description: Sets a single value within a specific record at a specific field.
# Parameters:
#   $1 - Record index (0-based)
#   $2 - Field name
#   $3 - New value (will be converted to JSON type by jq)
#   $4 - (Optional) File path. If not provided, uses loaded content.
# Returns: 0 on success, 1 on failure.
# Example: bejson_set_value 0 "Title" "The Updated C++ Language" "data.json"
bejson_set_value() {
    local record_index="$1"
    local field_name="$2"
    local new_value="$3"
    local file_path="$4"
    local content
    
    if [[ -z "$record_index" || -z "$field_name" || -z "$new_value" ]]; then
        bejson_error "Missing required parameters for bejson_set_value"
        echo "Usage: bejson_set_value    [file_path]"
        return 1
    fi
    
    if ! _bejson_get_content_or_load "$file_path" content; then return 1; fi
    
    local field_index=$(echo "$content" | jq --arg fname "$field_name" '[.Fields[].name] | index($fname)')
    if [[ "$field_index" == "null" ]]; then
        bejson_error "Field not found: $field_name"
        return 1
    fi
    
    local total_records=$(echo "$content" | jq '.Values | length')
    if [[ "$record_index" -ge "$total_records" ]] || [[ "$record_index" -lt 0 ]]; then
        bejson_error "Invalid record index: $record_index (total records: $total_records)"
        return 1
    fi
    
    # Attempt to parse new_value as JSON, if it fails, treat as string
    local new_value_json
    if echo "$new_value" | jq empty 2>/dev/null; then
        new_value_json="$new_value"
    else
        new_value_json=$(jq -n --arg val "$new_value" '$val') # Quote as string
    fi

    local updated_content
    updated_content=$(echo "$content" | jq --argjson newval "$new_value_json" --argjson ridx "$record_index" --argjson fidx "$field_index" '.Values[$ridx][$fidx] = $newval')
    
    if [[ $? -eq 0 ]]; then
        if ! _bejson_write_content_back "$file_path" "$updated_content"; then return 1; fi
        bejson_success "Value for record $record_index, field '$field_name' updated."
        return 0
    else
        bejson_error "Failed to set value for record $record_index, field '$field_name'."
        return 1
    fi
}

### FUNCTION: bejson_set_field_property ###
# Description: Modifies a property (e.g., 'name' or 'type') of a field definition.
# Parameters:
#   $1 - Field name (current name)
#   $2 - Property name to change (e.g., "name", "type")
#   $3 - New property value
#   $4 - (Optional) File path. If not provided, uses loaded content.
# Returns: 0 on success, 1 on failure.
# Example: bejson_set_field_property "OldID" "name" "NewID" "data.json"
bejson_set_field_property() {
    local field_name="$1"
    local property_name="$2"
    local new_property_value="$3"
    local file_path="$4"
    local content
    
    if [[ -z "$field_name" || -z "$property_name" || -z "$new_property_value" ]]; then
        bejson_error "Missing required parameters for bejson_set_field_property"
        echo "Usage: bejson_set_field_property    [file_path]"
        return 1
    fi
    
    if ! _bejson_get_content_or_load "$file_path" content; then return 1; fi
    
    local field_index=$(echo "$content" | jq --arg fname "$field_name" '[.Fields[].name] | index($fname)')
    if [[ "$field_index" == "null" ]]; then
        bejson_error "Field definition not found for name: $field_name"
        return 1
    fi

    # Validate property_name
    case "$property_name" in
        "name"|"type")
            ;;
        *)
            bejson_error "Invalid property name: $property_name. Only 'name' or 'type' can be modified."
            return 1
            ;;
    esac
    
    # Attempt to parse new_property_value as JSON, if it fails, treat as string
    local new_prop_val_json
    if echo "$new_property_value" | jq empty 2>/dev/null; then
        new_prop_val_json="$new_property_value"
    else
        new_prop_val_json=$(jq -n --arg val "$new_property_value" '$val') # Quote as string
    fi

    local updated_content
    updated_content=$(echo "$content" | jq --argjson fidx "$field_index" --arg prop "$property_name" --argjson newval "$new_prop_val_json" '.Fields[$fidx][$prop] = $newval')
    
    if [[ $? -eq 0 ]]; then
        if ! _bejson_write_content_back "$file_path" "$updated_content"; then return 1; fi
        bejson_success "Field '$field_name' property '$property_name' updated to '$new_property_value'."
        return 0
    else
        bejson_error "Failed to update field '$field_name' property '$property_name'."
        return 1
    fi
}

### FUNCTION: bejson_set_multiple_field_properties ###
# Description: Modifies a specific property (e.g., 'type') for multiple field definitions.
# Parameters:
#   $1 - JSON array of field names to update (e.g., '["ID", "Title"]')
#   $2 - Property name to change (e.g., "type")
#   $3 - New property value
#   $4 - (Optional) File path. If not provided, uses loaded content.
# Returns: 0 on success, 1 on failure.
# Example: bejson_set_multiple_field_properties '["ID", "ISBN"]' "type" "string" "data.json"
bejson_set_multiple_field_properties() {
    local field_names_json="$1"
    local property_name="$2"
    local new_property_value="$3"
    local file_path="$4"
    local content
    
    if [[ -z "$field_names_json" || -z "$property_name" || -z "$new_property_value" ]]; then
        bejson_error "Missing required parameters for bejson_set_multiple_field_properties"
        echo "Usage: bejson_set_multiple_field_properties    [file_path]"
        return 1
    fi
    
    if ! _bejson_get_content_or_load "$file_path" content; then return 1; fi
    
    if ! echo "$field_names_json" | jq empty 2>/dev/null; then
        bejson_error "Invalid JSON format for field_names_json parameter"
        return 1
    fi

    # Validate property_name
    case "$property_name" in
        "name"|"type")
            ;;
        *)
            bejson_error "Invalid property name: $property_name. Only 'name' or 'type' can be modified."
            return 1
            ;;
    esac

    local updated_content="$content"
    local success_count=0
    local failure_count=0

    # Attempt to parse new_property_value as JSON, if it fails, treat as string
    local new_prop_val_json
    if echo "$new_property_value" | jq empty 2>/dev/null; then
        new_prop_val_json="$new_property_value"
    else
        new_prop_val_json=$(jq -n --arg val "$new_property_value" '$val') # Quote as string
    fi
    
    # Construct a single jq command for efficiency
    local jq_command="."
    # Read field names into a bash array
    local field_names_array=()
    while IFS=$'
' read -r line; do
        field_names_array+=("$line")
    done < <(echo "$field_names_json" | jq -r '.[]')
    
    for field_name in "${field_names_array[@]}"; do
        local field_index=$(echo "$updated_content" | jq --arg fname "$field_name" '[.Fields[].name] | index($fname)')
        if [[ "$field_index" == "null" ]]; then
            bejson_error "Field definition not found for name: $field_name. Skipping."
            failure_count=$((failure_count + 1))
            continue
        fi
        jq_command+=" | .Fields[$field_index].$property_name = $new_prop_val_json"
        success_count=$((success_count + 1))
    done

    if [[ "$success_count" -gt 0 ]]; then
        updated_content=$(echo "$content" | jq "$jq_command")
        if [[ $? -eq 0 ]]; then
            if ! _bejson_write_content_back "$file_path" "$updated_content"; then return 1; fi
            bejson_success "$success_count field properties updated. $failure_count fields skipped."
            return 0
        else
            bejson_error "Failed to update multiple field properties."
            return 1
        fi
    else
        bejson_error "No valid fields found to update properties."
        return 1
    fi
}

### FUNCTION: bejson_set_multiple_records ###
# Description: Replaces a contiguous range of records with new records.
# Parameters:
#   $1 - Start index (0-based)
#   $2 - End index (0-based, inclusive)
#   $3 - New values JSON array of arrays (e.g., '[["val1", "val2"], ["val3", "val4"]]')
#   $4 - (Optional) File path. If not provided, uses loaded content.
# Returns: 0 on success, 1 on failure.
# Example: bejson_set_multiple_records 0 1 '[["new_isbn_A", "Title A"], ["new_isbn_B", "Title B"]]' "data.json"
bejson_set_multiple_records() {
    local start_index="$1"
    local end_index="$2"
    local new_values_json_array_of_arrays="$3"
    local file_path="$4"
    local content
    
    if [[ -z "$start_index" || -z "$end_index" || -z "$new_values_json_array_of_arrays" ]]; then
        bejson_error "Missing required parameters for bejson_set_multiple_records"
        echo "Usage: bejson_set_multiple_records    [file_path]"
        return 1
    fi
    
    if ! _bejson_get_content_or_load "$file_path" content; then return 1; fi
    
    # Validate new_values_json_array_of_arrays
    if ! echo "$new_values_json_array_of_arrays" | jq empty 2>/dev/null; then
        bejson_error "Invalid JSON format for new_values_json_array_of_arrays parameter"
        return 1
    fi
    
    local total_records=$(echo "$content" | jq '.Values | length')
    if [[ "$start_index" -lt 0 ]] || [[ "$end_index" -ge "$total_records" ]] || [[ "$start_index" -gt "$end_index" ]]; then
        bejson_error "Invalid index range: $start_index to $end_index (total records: $total_records)"
        return 1
    fi

    local fields_count=$(echo "$content" | jq '.Fields | length')
    local new_records_count=$(echo "$new_values_json_array_of_arrays" | jq 'length')
    
    if [[ "$((end_index - start_index + 1))" -ne "$new_records_count" ]]; then
        bejson_error "Number of new records ($new_records_count) does not match the range size ($((end_index - start_index + 1)))."
        return 1
    fi

    local i
    for ((i=0; i   [file_path]"
        return 1
    fi
    
    if ! _bejson_get_content_or_load "$file_path" content; then return 1; fi
    
    if ! echo "$record_indices_json" | jq empty 2>/dev/null; then
        bejson_error "Invalid JSON format for record_indices_json parameter"
        return 1
    fi

    local field_index=$(echo "$content" | jq --arg fname "$field_name" '[.Fields[].name] | index($fname)')
    if [[ "$field_index" == "null" ]]; then
        bejson_error "Field not found: $field_name"
        return 1
    fi
    
    local total_records=$(echo "$content" | jq '.Values | length')
    local updated_content="$content"
    local success_count=0
    local failure_count=0

    # Attempt to parse new_value as JSON, if it fails, treat as string
    local new_value_json
    if echo "$new_value" | jq empty 2>/dev/null; then
        new_value_json="$new_value"
    else
        new_value_json=$(jq -n --arg val "$new_value" '$val') # Quote as string
    fi

    local jq_command="."
    local record_indices_array=()
    while IFS=$'
' read -r line; do
        record_indices_array+=("$line")
    done < <(echo "$record_indices_json" | jq -r '.[]')

    for ridx in "${record_indices_array[@]}"; do
        if [[ "$ridx" -ge "$total_records" ]] || [[ "$ridx" -lt 0 ]]; then
            bejson_error "Invalid record index: $ridx. Skipping."
            failure_count=$((failure_count + 1))
            continue
        fi
        jq_command+=" | .Values[$ridx][$field_index] = $new_value_json"
        success_count=$((success_count + 1))
    done

    if [[ "$success_count" -gt 0 ]]; then
        updated_content=$(echo "$content" | jq "$jq_command")
        if [[ $? -eq 0 ]]; then
            if ! _bejson_write_content_back "$file_path" "$updated_content"; then return 1; fi
            bejson_success "$success_count values for field '$field_name' updated across multiple records. $failure_count records skipped."
            return 0
        else
            bejson_error "Failed to update multiple values for field '$field_name'."
            return 1
        fi
    else
        bejson_error "No valid records found to update values for field '$field_name'."
        return 1
    fi
}

### FUNCTION: bejson_delete_record ###
# Description: Deletes a record at a specified index.
# Parameters:
#   $1 - Record index (0-based)
#   $2 - (Optional) File path. If not provided, uses loaded content.
# Returns: 0 on success, 1 on failure.
# Example: bejson_delete_record 1 "data.json"
bejson_delete_record() {
    local record_index="$1"
    local file_path="$2"
    local content
    
    if [[ -z "$record_index" ]]; then
        bejson_error "Missing record index parameter for bejson_delete_record"
        echo "Usage: bejson_delete_record  [file_path]"
        return 1
    fi
    
    if ! _bejson_get_content_or_load "$file_path" content; then return 1; fi
    
    local total_records=$(echo "$content" | jq '.Values | length')
    if [[ "$record_index" -ge "$total_records" ]] || [[ "$record_index" -lt 0 ]]; then
        bejson_error "Invalid record index: $record_index (total records: $total_records)"
        return 1
    fi
    
    local updated_content
    updated_content=$(echo "$content" | jq --argjson idx "$record_index" 'del(.Values[$idx])')
    
    if [[ $? -eq 0 ]]; then
        if ! _bejson_write_content_back "$file_path" "$updated_content"; then return 1; fi
        bejson_success "Record $record_index deleted."
        return 0
    else
        bejson_error "Failed to delete record $record_index."
        return 1
    fi
}

################################################################################
###                    DATA SELECTION FUNCTIONS                             ###
################################################################################

# Global variable for selected records persistence
declare -g BEJSON_SELECTED_PERSIST_FILE=""

### FUNCTION: bejson_select ###
# Description: Selects records based on a jq filter expression. The filter is applied
#              to each item in the '.Values' array.
# Parameters:
#   $1 - jq filter expression (e.g., 'select(.[1] == "Some Title")' or 'select(.[0] > 100)')
#   $2 - (Optional) File path. If not provided, uses loaded content.
# Returns: Prints the matching records as a JSON array. 0 on success, 1 on failure.
# Example: bejson_select 'select(.[1] == "The C++ Programming Language")' "data.json"
bejson_select() {
    local jq_filter_expression="$1"
    local file_path="$2"
    local content
    
    if [[ -z "$jq_filter_expression" ]]; then
        bejson_error "Missing jq filter expression parameter for bejson_select"
        echo "Usage: bejson_select  [file_path]"
        return 1
    fi
    
    if ! _bejson_get_content_or_load "$file_path" content; then return 1; fi
    
    local result
    # Apply the filter to each element of the .Values array and collect results
    result=$(echo "$content" | jq -c "[.Values[] | $jq_filter_expression]")
    
    if [[ $? -eq 0 ]]; then
        echo "$result"
        return 0
    else
        bejson_error "jq filter failed. Check expression: $jq_filter_expression"
        return 1
    fi
}

### FUNCTION: bejson_select_persist ###
# Description: Selects records using a jq filter and stores them in a temporary BEJSON file.
#              The temporary file will contain a valid BEJSON 104 structure with the selected records.
# Parameters:
#   $1 - jq filter expression (e.g., 'select(.[2] > 2020)')
#   $2 - (Optional) File path to the source BEJSON file. If not provided, uses loaded content.
#   $3 - (Optional) Output temporary file path. If not provided, a unique temp file is generated.
# Returns: Prints the path to the temporary BEJSON file. 0 on success, 1 on failure.
# Side Effects: Sets BEJSON_SELECTED_PERSIST_FILE global variable.
# Example: temp_file=$(bejson_select_persist 'select(.[1] == "The C++ Programming Language")' "data.json")
bejson_select_persist() {
    local jq_filter_expression="$1"
    local source_file_path="$2"
    local output_temp_file="${3:-$(mktemp /tmp/bejson_selected_XXXXXX.json)}"
    local content
    
    if [[ -z "$jq_filter_expression" ]]; then
        bejson_error "Missing jq filter expression parameter for bejson_select_persist"
        echo "Usage: bejson_select_persist  [source_file_path] [output_temp_file]"
        return 1
    fi
    
    if ! _bejson_get_content_or_load "$source_file_path" content; then return 1; fi
    
    local selected_values
    selected_values=$(echo "$content" | jq -c "[.Values[] | $jq_filter_expression]")
    
    if [[ $? -ne 0 ]]; then
        bejson_error "jq filter failed during selection for persistence. Check expression: $jq_filter_expression"
        return 1
    fi

    # Get original metadata
    local format_data=$(echo "$content" | jq -c '{
        "Format": .Format,
        "Format_Version": .Format_Version,
        "Format_Creator": .Format_Creator,
        "Parent_Hierarchy": .Parent_Hierarchy // null,
        "Records_Type": .Records_Type,
        "Fields": .Fields
    }')

    # Construct the new BEJSON content with selected values
    local new_bejson_content
    new_bejson_content=$(echo "$format_data" | jq --argjson selected_vals "$selected_values" '.Values = $selected_vals')

    if [[ $? -ne 0 ]]; then
        bejson_error "Failed to construct new BEJSON content for persistence."
        return 1
    fi

    echo "$new_bejson_content" > "$output_temp_file"
    if [[ $? -eq 0 ]]; then
        BEJSON_SELECTED_PERSIST_FILE="$output_temp_file"
        bejson_success "Selected records persisted to temporary file: $output_temp_file"
        echo "$output_temp_file"
        return 0
    else
        bejson_error "Failed to write selected records to temporary file: $output_temp_file"
        return 1
    fi
}

### FUNCTION: bejson_remove_selected_persist ###
# Description: Deletes the temporary file created by bejson_select_persist.
# Parameters:
#   $1 - (Optional) Path to the temporary file. If not provided, uses the globally stored path.
# Returns: 0 on success, 1 on failure.
# Example: bejson_remove_selected_persist
# Example: bejson_remove_selected_persist "/tmp/my_temp_file.json"
bejson_remove_selected_persist() {
    local temp_file_path="${1:-$BEJSON_SELECTED_PERSIST_FILE}"
    
    if [[ -z "$temp_file_path" ]]; then
        bejson_error "No temporary file path provided or globally stored."
        return 1
    fi
    
    if [[ -f "$temp_file_path" ]]; then
        rm "$temp_file_path"
        if [[ $? -eq 0 ]]; then
            if [[ "$temp_file_path" == "$BEJSON_SELECTED_PERSIST_FILE" ]]; then
                BEJSON_SELECTED_PERSIST_FILE="" # Clear global if it was the one deleted
            fi
            bejson_success "Temporary file removed: $temp_file_path"
            return 0
        else
            bejson_error "Failed to remove temporary file: $temp_file_path"
            return 1
        fi
    else
        bejson_error "Temporary file not found: $temp_file_path"
        return 1
    fi
}

################################################################################
###                    LIBRARY INITIALIZATION                               ###
################################################################################

# Check dependencies on library load
if ! bejson_check_dependencies; then
    bejson_error "Library initialization failed due to missing dependencies"
    return 1 2>/dev/null || exit 1
fi

# Print library loaded message
echo "BEJSON 104 Library v1.0.0 loaded successfully"
echo "Use 'bejson_' prefix to access library functions"
echo "Example: bejson_create_file, bejson_load_file, bejson_validate_file"