jsonlogic2sql

package module
v1.0.6 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Feb 9, 2026 License: MIT Imports: 10 Imported by: 0

README

JSON Logic to SQL Transpiler

A Go library that converts JSON Logic expressions into SQL. This library provides a clean, type-safe API for transforming JSON Logic rules into SQL WHERE clauses or standalone conditions, with support for multiple SQL dialects.

Features

  • Complete JSON Logic Support: Implements all core JSON Logic operators
  • SQL Dialect Support: Target BigQuery, Spanner, PostgreSQL, DuckDB, or ClickHouse
  • Custom Operators: Extensible registry pattern for custom SQL functions
  • Schema Validation: Optional field schema for strict column validation
  • Structured Errors: Error codes and JSONPath locations for debugging
  • Library & CLI: Both programmatic API and interactive REPL

Quick Start

go get github.com/h22rana/jsonlogic2sql@latest
package main

import (
    "fmt"
    "github.com/h22rana/jsonlogic2sql"
)

func main() {
    sql, err := jsonlogic2sql.Transpile(
        jsonlogic2sql.DialectBigQuery,
        `{">": [{"var": "amount"}, 1000]}`,
    )
    if err != nil {
        panic(err)
    }
    fmt.Println(sql) // Output: WHERE amount > 1000
}

Supported Operators

Category Operators
Data Access var, missing, missing_some
Comparison ==, ===, !=, !==, >, >=, <, <=
Logical and, or, !, !!, if
Numeric +, -, *, /, %, max, min
Array in, map, filter, reduce, all, some, none, merge
String in, cat, substr

Supported Dialects

Dialect Constant
Google BigQuery DialectBigQuery
Google Cloud Spanner DialectSpanner
PostgreSQL DialectPostgreSQL
DuckDB DialectDuckDB
ClickHouse DialectClickHouse

Documentation

Important Notes

Semantic Correctness Assumption: This library assumes that the input JSONLogic is semantically correct. The transpiler generates SQL that directly corresponds to the JSONLogic structure without validating the logical correctness of the expressions.

SQL Injection: This library does NOT handle SQL injection prevention. The caller is responsible for validating input and using parameterized queries where appropriate.

Interactive REPL

make run
[BigQuery] jsonlogic> {">": [{"var": "amount"}, 1000]}
SQL: WHERE amount > 1000

[BigQuery] jsonlogic> :dialect
Select dialect: PostgreSQL

[PostgreSQL] jsonlogic> {"merge": [{"var": "a"}, {"var": "b"}]}
SQL: WHERE (a || b)

Development

make test      # Run all tests (3,000+ test cases)
make build     # Build REPL binary
make lint      # Run linter
make run       # Run REPL

License

This project is licensed under the MIT License - see the LICENSE file for details.

Documentation

Overview

Package jsonlogic2sql exports error types for programmatic error handling.

Index

Constants

View Source
const (
	// Structural/validation errors (E001-E099).
	ErrInvalidExpression   = tperrors.ErrInvalidExpression
	ErrEmptyArray          = tperrors.ErrEmptyArray
	ErrMultipleKeys        = tperrors.ErrMultipleKeys
	ErrPrimitiveNotAllowed = tperrors.ErrPrimitiveNotAllowed
	ErrArrayNotAllowed     = tperrors.ErrArrayNotAllowed
	ErrValidation          = tperrors.ErrValidation
	ErrInvalidJSON         = tperrors.ErrInvalidJSON

	// Operator-specific errors (E100-E199).
	ErrUnsupportedOperator   = tperrors.ErrUnsupportedOperator
	ErrOperatorRequiresArray = tperrors.ErrOperatorRequiresArray
	ErrCustomOperatorFailed  = tperrors.ErrCustomOperatorFailed

	// Type/schema errors (E200-E299).
	ErrTypeMismatch     = tperrors.ErrTypeMismatch
	ErrFieldNotInSchema = tperrors.ErrFieldNotInSchema
	ErrInvalidFieldType = tperrors.ErrInvalidFieldType
	ErrInvalidEnumValue = tperrors.ErrInvalidEnumValue

	// Argument errors (E300-E399).
	ErrInsufficientArgs    = tperrors.ErrInsufficientArgs
	ErrTooManyArgs         = tperrors.ErrTooManyArgs
	ErrInvalidArgument     = tperrors.ErrInvalidArgument
	ErrInvalidArgType      = tperrors.ErrInvalidArgType
	ErrInvalidDefaultValue = tperrors.ErrInvalidDefaultValue
)

Error codes for programmatic error handling.

View Source
const (
	DialectBigQuery   = dialect.DialectBigQuery
	DialectSpanner    = dialect.DialectSpanner
	DialectPostgreSQL = dialect.DialectPostgreSQL
	DialectDuckDB     = dialect.DialectDuckDB
	DialectClickHouse = dialect.DialectClickHouse
)

Re-export dialect constants for public API.

Variables

This section is empty.

Functions

func IsErrorCode added in v1.0.2

func IsErrorCode(err error, code ErrorCode) bool

IsErrorCode checks if an error has a specific error code. Returns true if the error is or wraps a TranspileError with the given code.

Example:

if jsonlogic2sql.IsErrorCode(err, jsonlogic2sql.ErrUnsupportedOperator) {
    fmt.Println("Unknown operator used")
}

func Transpile

func Transpile(d Dialect, jsonLogic string) (string, error)

Transpile converts a JSON Logic string to a SQL WHERE clause. Dialect is required - use DialectBigQuery or DialectSpanner.

func TranspileCondition added in v1.0.3

func TranspileCondition(d Dialect, jsonLogic string) (string, error)

TranspileCondition converts a JSON Logic string to a SQL condition without the WHERE keyword. Dialect is required - use DialectBigQuery, DialectSpanner, DialectPostgreSQL, or DialectDuckDB.

func TranspileConditionFromInterface added in v1.0.3

func TranspileConditionFromInterface(d Dialect, logic interface{}) (string, error)

TranspileConditionFromInterface converts any JSON Logic interface{} to a SQL condition without the WHERE keyword. Dialect is required - use DialectBigQuery, DialectSpanner, DialectPostgreSQL, or DialectDuckDB.

func TranspileConditionFromMap added in v1.0.3

func TranspileConditionFromMap(d Dialect, logic map[string]interface{}) (string, error)

TranspileConditionFromMap converts a pre-parsed JSON Logic map to a SQL condition without the WHERE keyword. Dialect is required - use DialectBigQuery, DialectSpanner, DialectPostgreSQL, or DialectDuckDB.

func TranspileFromInterface

func TranspileFromInterface(d Dialect, logic interface{}) (string, error)

TranspileFromInterface converts any JSON Logic interface{} to a SQL WHERE clause. Dialect is required - use DialectBigQuery or DialectSpanner.

func TranspileFromMap

func TranspileFromMap(d Dialect, logic map[string]interface{}) (string, error)

TranspileFromMap converts a pre-parsed JSON Logic map to a SQL WHERE clause. Dialect is required - use DialectBigQuery or DialectSpanner.

Types

type Dialect added in v1.0.2

type Dialect = dialect.Dialect

Dialect is the type for SQL dialect selection.

type DialectAwareOperatorFunc added in v1.0.2

type DialectAwareOperatorFunc func(operator string, args []interface{}, dialect Dialect) (string, error)

DialectAwareOperatorFunc is a function type for dialect-aware custom operator implementations. It receives the operator name, arguments, and the target SQL dialect.

Example:

nowOp := func(operator string, args []interface{}, dialect Dialect) (string, error) {
    switch dialect {
    case DialectBigQuery:
        return "CURRENT_TIMESTAMP()", nil
    case DialectSpanner:
        return "CURRENT_TIMESTAMP()", nil
    default:
        return "", fmt.Errorf("unsupported dialect: %s", dialect)
    }
}

type DialectAwareOperatorHandler added in v1.0.2

type DialectAwareOperatorHandler interface {
	// ToSQLWithDialect converts the operator and its arguments to SQL for the specified dialect.
	// The args slice contains the SQL representations of each argument.
	ToSQLWithDialect(operator string, args []interface{}, dialect Dialect) (string, error)
}

DialectAwareOperatorHandler is an interface for dialect-aware custom operator implementations. Implement this interface when your operator needs to generate different SQL for different dialects.

Example:

type CurrentTimeOperator struct{}

func (c *CurrentTimeOperator) ToSQLWithDialect(operator string, args []interface{}, dialect Dialect) (string, error) {
    switch dialect {
    case DialectBigQuery:
        return "CURRENT_TIMESTAMP()", nil
    case DialectSpanner:
        return "CURRENT_TIMESTAMP()", nil
    default:
        return "", fmt.Errorf("unsupported dialect: %s", dialect)
    }
}

type ErrorCode added in v1.0.2

type ErrorCode = tperrors.ErrorCode

ErrorCode represents a specific error condition. Codes are organized by category:

  • E001-E099: Structural/validation errors
  • E100-E199: Operator-specific errors
  • E200-E299: Type/schema errors
  • E300-E399: Argument errors

type FieldSchema added in v1.0.1

type FieldSchema struct {
	Name          string    `json:"name"`
	Type          FieldType `json:"type"`
	AllowedValues []string  `json:"allowedValues,omitempty"` // For enum types: list of valid values
}

FieldSchema represents the schema/metadata for a single field.

type FieldType added in v1.0.1

type FieldType string

FieldType represents the type of a field in the schema.

const (
	FieldTypeString  FieldType = "string"
	FieldTypeInteger FieldType = "integer"
	FieldTypeNumber  FieldType = "number"
	FieldTypeBoolean FieldType = "boolean"
	FieldTypeArray   FieldType = "array"
	FieldTypeObject  FieldType = "object"
	FieldTypeEnum    FieldType = "enum"
)

Field type constants for schema validation.

type OperatorFunc added in v1.0.1

type OperatorFunc func(operator string, args []interface{}) (string, error)

OperatorFunc is a function type for custom operator implementations. It receives the operator name and its arguments, and returns the SQL representation.

Example:

lengthOp := func(operator string, args []interface{}) (string, error) {
    if len(args) != 1 {
        return "", fmt.Errorf("length requires exactly 1 argument")
    }
    // args[0] will be the SQL representation of the argument
    return fmt.Sprintf("LENGTH(%s)", args[0]), nil
}

type OperatorHandler added in v1.0.1

type OperatorHandler interface {
	// ToSQL converts the operator and its arguments to SQL.
	// The args slice contains the SQL representations of each argument.
	ToSQL(operator string, args []interface{}) (string, error)
}

OperatorHandler is an interface for custom operator implementations. Implement this interface for more complex operators that need state.

Example:

type MyOperator struct {
    prefix string
}

func (m *MyOperator) ToSQL(operator string, args []interface{}) (string, error) {
    return fmt.Sprintf("%s_%s", m.prefix, args[0]), nil
}

type OperatorRegistry added in v1.0.1

type OperatorRegistry struct {
	// contains filtered or unexported fields
}

OperatorRegistry manages custom operator registrations. It is thread-safe and can be used concurrently.

func NewOperatorRegistry added in v1.0.1

func NewOperatorRegistry() *OperatorRegistry

NewOperatorRegistry creates a new empty operator registry.

func (*OperatorRegistry) Clear added in v1.0.1

func (r *OperatorRegistry) Clear()

Clear removes all registered operators.

func (*OperatorRegistry) Clone added in v1.0.1

func (r *OperatorRegistry) Clone() *OperatorRegistry

Clone creates a copy of the registry with all registered operators.

func (*OperatorRegistry) Get added in v1.0.1

func (r *OperatorRegistry) Get(operatorName string) (OperatorHandler, bool)

Get retrieves a custom operator handler from the registry. Returns the handler and true if found, nil and false otherwise.

func (*OperatorRegistry) Has added in v1.0.1

func (r *OperatorRegistry) Has(operatorName string) bool

Has checks if an operator is registered.

func (*OperatorRegistry) List added in v1.0.1

func (r *OperatorRegistry) List() []string

List returns a slice of all registered operator names.

func (*OperatorRegistry) Merge added in v1.0.1

func (r *OperatorRegistry) Merge(other *OperatorRegistry)

Merge adds all operators from another registry to this one. Existing operators with the same name will be replaced.

func (*OperatorRegistry) Register added in v1.0.1

func (r *OperatorRegistry) Register(operatorName string, handler OperatorHandler)

Register adds a custom operator handler to the registry. If an operator with the same name already exists, it will be replaced.

Example:

registry := NewOperatorRegistry()
registry.Register("length", &LengthOperator{})

func (*OperatorRegistry) RegisterDialectAwareFunc added in v1.0.2

func (r *OperatorRegistry) RegisterDialectAwareFunc(operatorName string, fn DialectAwareOperatorFunc)

RegisterDialectAwareFunc adds a dialect-aware custom operator function to the registry. Use this for operators that need to generate different SQL based on the target dialect.

Example:

registry := NewOperatorRegistry()
registry.RegisterDialectAwareFunc("now", func(op string, args []interface{}, dialect Dialect) (string, error) {
    return "CURRENT_TIMESTAMP()", nil
})

func (*OperatorRegistry) RegisterFunc added in v1.0.1

func (r *OperatorRegistry) RegisterFunc(operatorName string, fn OperatorFunc)

RegisterFunc adds a custom operator function to the registry. This is a convenience method for simple operators that don't need state.

Example:

registry := NewOperatorRegistry()
registry.RegisterFunc("length", func(op string, args []interface{}) (string, error) {
    return fmt.Sprintf("LENGTH(%s)", args[0]), nil
})

func (*OperatorRegistry) Unregister added in v1.0.1

func (r *OperatorRegistry) Unregister(operatorName string) bool

Unregister removes a custom operator from the registry. Returns true if the operator was found and removed, false otherwise.

type Schema added in v1.0.1

type Schema struct {
	// contains filtered or unexported fields
}

Schema represents the collection of field schemas.

func NewSchema added in v1.0.1

func NewSchema(fields []FieldSchema) *Schema

NewSchema creates a new schema from a slice of field schemas.

func NewSchemaFromFile added in v1.0.1

func NewSchemaFromFile(filepath string) (*Schema, error)

NewSchemaFromFile loads a schema from a JSON file.

func NewSchemaFromJSON added in v1.0.1

func NewSchemaFromJSON(data []byte) (*Schema, error)

NewSchemaFromJSON creates a new schema from a JSON byte slice.

func (*Schema) GetAllowedValues added in v1.0.1

func (s *Schema) GetAllowedValues(fieldName string) []string

GetAllowedValues returns the allowed values for an enum field Returns nil if the field is not an enum or doesn't exist.

func (*Schema) GetFieldType added in v1.0.1

func (s *Schema) GetFieldType(fieldName string) string

GetFieldType returns the type of a field as a string. This implements the operators.SchemaProvider interface.

func (*Schema) GetFieldTypeFieldType added in v1.0.1

func (s *Schema) GetFieldTypeFieldType(fieldName string) FieldType

GetFieldTypeFieldType returns the type of a field as FieldType (internal use).

func (*Schema) GetFields added in v1.0.1

func (s *Schema) GetFields() []string

GetFields returns all field names in the schema.

func (*Schema) HasField added in v1.0.1

func (s *Schema) HasField(fieldName string) bool

HasField checks if a field exists in the schema.

func (*Schema) IsArrayType added in v1.0.1

func (s *Schema) IsArrayType(fieldName string) bool

IsArrayType checks if a field is of array type.

func (*Schema) IsBooleanType added in v1.0.1

func (s *Schema) IsBooleanType(fieldName string) bool

IsBooleanType checks if a field is of boolean type.

func (*Schema) IsEnumType added in v1.0.1

func (s *Schema) IsEnumType(fieldName string) bool

IsEnumType checks if a field is of enum type.

func (*Schema) IsNumericType added in v1.0.1

func (s *Schema) IsNumericType(fieldName string) bool

IsNumericType checks if a field is of numeric type (integer or number).

func (*Schema) IsStringType added in v1.0.1

func (s *Schema) IsStringType(fieldName string) bool

IsStringType checks if a field is of string type.

func (*Schema) ValidateEnumValue added in v1.0.1

func (s *Schema) ValidateEnumValue(fieldName, value string) error

ValidateEnumValue checks if a value is valid for an enum field Returns nil if valid, error if invalid.

func (*Schema) ValidateField added in v1.0.1

func (s *Schema) ValidateField(fieldName string) error

ValidateField checks if a field exists in the schema and returns an error if not.

type TranspileError added in v1.0.2

type TranspileError = tperrors.TranspileError

TranspileError is the structured error type returned by transpilation operations. Use errors.As to extract this type for programmatic error handling.

Example:

sql, err := transpiler.Transpile(jsonLogic)
if err != nil {
    var tpErr *TranspileError
    if errors.As(err, &tpErr) {
        fmt.Printf("Error code: %s\n", tpErr.Code)
        fmt.Printf("Path: %s\n", tpErr.Path)
        fmt.Printf("Operator: %s\n", tpErr.Operator)
    }
}

func AsTranspileError added in v1.0.2

func AsTranspileError(err error) (*TranspileError, bool)

AsTranspileError attempts to extract a TranspileError from an error. Returns the TranspileError and true if the error is or wraps a TranspileError, otherwise returns nil and false.

Example:

sql, err := transpiler.Transpile(jsonLogic)
if tpErr, ok := jsonlogic2sql.AsTranspileError(err); ok {
    fmt.Printf("Error code: %s\n", tpErr.Code)
}

type Transpiler

type Transpiler struct {
	// contains filtered or unexported fields
}

Transpiler provides the main API for converting JSON Logic to SQL WHERE clauses.

func NewTranspiler

func NewTranspiler(d Dialect) (*Transpiler, error)

NewTranspiler creates a new transpiler instance with the specified dialect. Dialect is required - use DialectBigQuery, DialectSpanner, DialectPostgreSQL, or DialectDuckDB.

func NewTranspilerWithConfig

func NewTranspilerWithConfig(config *TranspilerConfig) (*Transpiler, error)

NewTranspilerWithConfig creates a new transpiler instance with custom configuration. Config.Dialect is required - use DialectBigQuery or DialectSpanner.

func (*Transpiler) ClearCustomOperators added in v1.0.1

func (t *Transpiler) ClearCustomOperators()

ClearCustomOperators removes all registered custom operators.

func (*Transpiler) GetDialect added in v1.0.2

func (t *Transpiler) GetDialect() Dialect

GetDialect returns the configured dialect.

func (*Transpiler) HasCustomOperator added in v1.0.1

func (t *Transpiler) HasCustomOperator(name string) bool

HasCustomOperator checks if a custom operator is registered.

func (*Transpiler) ListCustomOperators added in v1.0.1

func (t *Transpiler) ListCustomOperators() []string

ListCustomOperators returns a slice of all registered custom operator names.

func (*Transpiler) RegisterDialectAwareOperator added in v1.0.2

func (t *Transpiler) RegisterDialectAwareOperator(name string, handler DialectAwareOperatorHandler) error

RegisterDialectAwareOperator registers a dialect-aware custom operator handler. Use this when your operator needs to generate different SQL based on the target dialect. Returns an error if the operator name conflicts with a built-in operator.

Example:

transpiler, _ := jsonlogic2sql.NewTranspiler(jsonlogic2sql.DialectBigQuery)
transpiler.RegisterDialectAwareOperator("now", &CurrentTimeOperator{})
sql, _ := transpiler.Transpile(`{"now": []}`)
// BigQuery: WHERE CURRENT_TIMESTAMP()
// Spanner: WHERE CURRENT_TIMESTAMP()

func (*Transpiler) RegisterDialectAwareOperatorFunc added in v1.0.2

func (t *Transpiler) RegisterDialectAwareOperatorFunc(name string, fn DialectAwareOperatorFunc) error

RegisterDialectAwareOperatorFunc registers a dialect-aware custom operator function. Use this for operators that need to generate different SQL based on the target dialect. Returns an error if the operator name conflicts with a built-in operator.

Example:

transpiler, _ := jsonlogic2sql.NewTranspiler(jsonlogic2sql.DialectBigQuery)
transpiler.RegisterDialectAwareOperatorFunc("now", func(op string, args []interface{}, dialect jsonlogic2sql.Dialect) (string, error) {
    switch dialect {
    case jsonlogic2sql.DialectBigQuery:
        return "CURRENT_TIMESTAMP()", nil
    case jsonlogic2sql.DialectSpanner:
        return "CURRENT_TIMESTAMP()", nil
    default:
        return "", fmt.Errorf("unsupported dialect: %s", dialect)
    }
})

func (*Transpiler) RegisterOperator added in v1.0.1

func (t *Transpiler) RegisterOperator(name string, handler OperatorHandler) error

RegisterOperator registers a custom operator handler. The handler will be called when the operator is encountered during transpilation. Returns an error if the operator name conflicts with a built-in operator.

Example:

transpiler, _ := jsonlogic2sql.NewTranspiler(jsonlogic2sql.DialectBigQuery)
transpiler.RegisterOperator("length", &LengthOperator{})
sql, _ := transpiler.Transpile(`{"length": [{"var": "email"}]}`)
// Output: WHERE LENGTH(email)

func (*Transpiler) RegisterOperatorFunc added in v1.0.1

func (t *Transpiler) RegisterOperatorFunc(name string, fn OperatorFunc) error

RegisterOperatorFunc registers a custom operator function. This is a convenience method for simple operators that don't need state. Returns an error if the operator name conflicts with a built-in operator.

Example:

transpiler, _ := jsonlogic2sql.NewTranspiler(jsonlogic2sql.DialectBigQuery)
transpiler.RegisterOperatorFunc("length", func(op string, args []interface{}) (string, error) {
    if len(args) != 1 {
        return "", fmt.Errorf("length requires exactly 1 argument")
    }
    return fmt.Sprintf("LENGTH(%s)", args[0]), nil
})
sql, _ := transpiler.Transpile(`{"length": [{"var": "email"}]}`)
// Output: WHERE LENGTH(email)

func (*Transpiler) SetSchema added in v1.0.1

func (t *Transpiler) SetSchema(schema *Schema)

SetSchema sets the schema for field validation and type checking This is optional - if not set, no schema validation will be performed.

func (*Transpiler) Transpile

func (t *Transpiler) Transpile(jsonLogic string) (string, error)

Transpile converts a JSON Logic string to a SQL WHERE clause.

func (*Transpiler) TranspileCondition added in v1.0.3

func (t *Transpiler) TranspileCondition(jsonLogic string) (string, error)

TranspileCondition converts a JSON Logic string to a SQL condition without the WHERE keyword. This is useful when you need to embed the condition in a larger query.

func (*Transpiler) TranspileConditionFromInterface added in v1.0.3

func (t *Transpiler) TranspileConditionFromInterface(logic interface{}) (string, error)

TranspileConditionFromInterface converts any JSON Logic interface{} to a SQL condition without the WHERE keyword.

func (*Transpiler) TranspileConditionFromMap added in v1.0.3

func (t *Transpiler) TranspileConditionFromMap(logic map[string]interface{}) (string, error)

TranspileConditionFromMap converts a pre-parsed JSON Logic map to a SQL condition without the WHERE keyword.

func (*Transpiler) TranspileFromInterface

func (t *Transpiler) TranspileFromInterface(logic interface{}) (string, error)

TranspileFromInterface converts any JSON Logic interface{} to a SQL WHERE clause.

func (*Transpiler) TranspileFromMap

func (t *Transpiler) TranspileFromMap(logic map[string]interface{}) (string, error)

TranspileFromMap converts a pre-parsed JSON Logic map to a SQL WHERE clause.

func (*Transpiler) UnregisterOperator added in v1.0.1

func (t *Transpiler) UnregisterOperator(name string) bool

UnregisterOperator removes a custom operator from the transpiler. Returns true if the operator was found and removed, false otherwise.

type TranspilerConfig

type TranspilerConfig struct {
	Dialect Dialect // Required: target SQL dialect
	Schema  *Schema // Optional schema for field validation
}

TranspilerConfig holds configuration options for the transpiler.

Directories

Path Synopsis
cmd
repl command
internal
dialect
Package dialect provides SQL dialect definitions for the transpiler.
Package dialect provides SQL dialect definitions for the transpiler.
errors
Package errors provides structured error types for jsonlogic2sql transpilation.
Package errors provides structured error types for jsonlogic2sql transpilation.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL