TinyRuleKit

JSON Best Practices

This page focuses only on writing maintainable TinyRuleKit JSON. Use it after the basic rule shape is clear and before a rule file becomes hard to review.

Start With The Simple Shape

Prefer a raw array for small rule files that only contain rules:

JSON
[
  {
    "id": "vip_join_reward",
    "trigger": "PLAYER_JOINED",
    "conditions": [
      {
        "type": "LEVEL_AT_LEAST",
        "params": {
          "level": 5
        }
      }
    ],
    "actions": [
      {
        "type": "GIVE_COINS",
        "params": {
          "amount": 25
        }
      }
    ]
  }
]

Every enabled rule must define one trigger, at least one condition, and at least one action. Use enabled: false for drafts; disabled rules are not resolved, compiled, or indexed.

Use Metadata When The File Needs It

Use the wrapped document format when the file needs requirements, trigger guards, condition definitions, or framework version constraints:

JSON
{
  "requires": [
    "EXPRESSIONS_ENABLED"
  ],
  "rules": [
    {
      "id": "weekend_bonus",
      "trigger": "PLAYER_JOINED",
      "conditions": [
        {
          "type": "IS_WEEKEND"
        }
      ],
      "actions": [
        {
          "type": "GIVE_COINS",
          "params": {
            "amount": 10
          }
        }
      ]
    }
  ]
}

When the builder knows the document shape, pass RuleDocumentFormat.SIMPLE for raw arrays or RuleDocumentFormat.SPECIAL for wrapped documents. If no format is provided, TinyRuleKit detects the shape automatically.

Keep Rules Reviewable

  • give stable id values to rules edited outside Java
  • keep each rule focused on one business case
  • keep parameter names small, typed, and validated by Java factories
  • prefer static conditions for domain concepts
  • use expressions only for small data checks
  • use when when only one action needs an extra guard
  • use triggerGuards when all rules under one trigger share the same precondition
  • use conditionDefs only for repeated or genuinely named condition concepts
  • split a rule when the trigger, intent, or resulting actions are different

Compose Deliberately

Use operators when they clarify the rule. If a composed condition becomes difficult to scan, move that concept behind a static condition token or a small condition definition.

Use compact negated groups to avoid one extra nesting level when the whole group is inverted:

JSON
{
  "!any": [
    {
      "type": "IS_BANNED"
    },
    {
      "type": "IS_SUSPENDED"
    }
  ]
}

Prefer explicit not when only one simple condition is inverted.

Avoid Over-Abstracting JSON

Reusable condition definitions are useful, but they are not free for readers. Do not create a definition only because a condition might be reused later.

Good uses:

  • the same condition group appears in several rules or guards
  • the name captures a real domain concept
  • the definition is shorter to read than the repeated JSON

Poor uses:

  • a definition is used only once without improving readability
  • a definition hides a large decision tree
  • nested definitions make reviewers jump through several names to understand one rule

Review Checklist

  • the document shape matches how the builder loads it
  • each enabled rule has conditions and actions
  • rule ids explain the behavior well enough for diagnostics
  • static tokens are used before expressions
  • expressions only read safe values exposed by the context
  • operator trees remain shallow enough to review
  • condition definitions are actually used
  • disabled rules are intentional drafts, not dead configuration
  • tests compile representative JSON and fire realistic contexts

Continue with RuleParams for typed parameters, defaults, lists, maps, and enum parameters.