JSON Flags

Structured remote configuration for multi-field settings, nested options, and typed objects without redeploys.

Introduction

JSON flags store a structured value — a JSON object {} or array []. Use them when configuration spans multiple related fields that should change together as a single unit.

Applications read JSON flags locally from an in-memory snapshot. Evaluation is synchronous — no network request occurs on the read path. SDKs deserialize the stored JSON into typed objects or return a deep copy of dict/list values.

Common use cases:

  • Feature module config — enable flags, limits, and UI options in one object
  • Checkout and payment settings — providers, timeouts, and display rules grouped together
  • Rollout configuration — percentage, allowlists, and variant metadata
  • Integration settings — third-party endpoints, retry policies, and feature toggles per integration
  • UI component config — labels, layout options, and visibility rules for a widget
  • Operational tuning — related numeric and Boolean fields that operators adjust as a set

When to use JSON flags

Use a JSON flag when configuration requires multiple related fields, nested structure, or a typed object — not a single scalar value.

Scenario Example key Example value Typical default
Checkout config checkout-config { "enableUpsells": true, "maxItems": 10 } Safe object with conservative fields
Rollout rules feature-rollout { "percentage": 25, "enabled": true } { "percentage": 0, "enabled": false }
Retry policy retry-policy { "maxAttempts": 3, "backoffMs": 500 } { "maxAttempts": 1, "backoffMs": 1000 }
Banner config promo-banner { "visible": true, "message": "Sale ends Sunday" } { "visible": false, "message": "" }

Checkout config — group related checkout behavior in one flag. Change all fields atomically from the dashboard.

Rollout rules — combine enablement with percentage or segment metadata. Default to disabled with zero rollout.

Retry policy — keep retry count and backoff together instead of separate Number flags that can drift out of sync.

Banner config — visibility and copy in one object. Default to hidden with empty message.

For a single on/off gate, use Boolean flags. For one text variant, use String flags. For one numeric limit, use Number flags.

Create a JSON flag

Create flags in the SOASAP dashboard. If you have not set up a project yet, start with Create Your First Flag.

  1. Open your project

    Sign in to the dashboard, select your project, and open the environment where you want to configure the initial value — typically Development for local work.

  2. Create the flag

    Add a new flag with the following properties:

    Key checkout-config
    Type JSON
    Initial value { "enableUpsells": false, "maxItems": 10 }
  3. Save and verify

    Save the flag. Connected SDKs receive the update over SSE. Read the flag in your application to confirm the deserialized object matches the dashboard for that environment.

Project vs environment. The flag definition (key and type) belongs to the project — defined once, shared across all environments. The flag value belongs to each environment. The same key can hold different JSON objects per environment.

Example configuration for checkout-config:

EnvironmentValue
Development { "enableUpsells": true, "maxItems": 50 }
Staging { "enableUpsells": true, "maxItems": 20 }
Production { "enableUpsells": false, "maxItems": 10 }

JSON property names in the dashboard are typically camelCase. SDKs deserialize into typed models according to platform conventions.

Evaluate a JSON flag

SDKs evaluate JSON flags locally from an in-memory snapshot. The getter deserializes or deep-copies the stored value synchronously — no HTTP request, no disk I/O, and no dependency on SOASAP Cloud availability at read time.

If the key is missing, the value is not a JSON object or array, or deserialization fails, the SDK returns your explicit default. Getters never throw.

Method names vary by SDK; the semantics are identical.

.NET

public sealed class CheckoutConfig
{
    public bool EnableUpsells { get; set; }
    public int MaxItems { get; set; }
}

var config = client.GetJson(
    "checkout-config",
    new CheckoutConfig { EnableUpsells = false, MaxItems = 10 });

JSON property names are matched case-insensitively — camelCase dashboard payloads deserialize into PascalCase C# properties without extra attributes.

Node.js

interface CheckoutConfig {
  enableUpsells: boolean;
  maxItems: number;
}

const config = flags.getJson<CheckoutConfig>("checkout-config", {
  enableUpsells: false,
  maxItems: 10,
});

Python

from typing import TypedDict

class CheckoutConfig(TypedDict):
    enableUpsells: bool
    maxItems: int

config = flags.get_json(
    "checkout-config",
    {"enableUpsells": False, "maxItems": 10},
)

Kotlin

import kotlinx.serialization.Serializable

@Serializable
data class CheckoutConfig(
    val enableUpsells: Boolean = false,
    val maxItems: Int = 10,
)

val config = client.getJson<CheckoutConfig>("checkout-config")
    ?: CheckoutConfig()

React / React Native

interface CheckoutConfig {
  enableUpsells: boolean;
  maxItems: number;
}

const config = useSoasapJson<CheckoutConfig>("checkout-config", {
  enableUpsells: false,
  maxItems: 10,
});

Angular

readonly config = this.soasap.json<CheckoutConfig>("checkout-config", {
  enableUpsells: false,
  maxItems: 10,
});

Flag evaluation never performs network requests. Values are read from the local SDK snapshot. Background synchronization (SSE) updates the snapshot; subsequent reads return the new value without a redeploy. See Local Evaluation.

Explicit defaults

Always provide a complete default object on every JSON read:

client.GetJson(
    "checkout-config",
    new CheckoutConfig { EnableUpsells = false, MaxItems = 10 });

The default is returned when:

  • The flag key does not exist in the synchronized snapshot
  • The flag was deleted or disabled in the dashboard for this environment
  • Synchronization has not completed and no cached snapshot is available
  • The restored disk or browser cache is empty or invalid
  • The stored value is not a JSON object or array (type mismatch)
  • Deserialization fails — unknown shape, invalid types, or schema mismatch

Explicit defaults are your production safety net. Define every field with a safe fallback value so missing or partial configuration never enables risky behavior.

Recommendation. Treat the default object as your production-safe baseline — conservative limits, features disabled, and empty collections. Every optional dashboard field should have a corresponding safe default in code.

Details: Default Values.

Common usage patterns

Feature module configuration

Load a typed config object and pass it to the module initializer.

var config = client.GetJson(
    "checkout-config",
    new CheckoutConfig { EnableUpsells = false, MaxItems = 10 });

if (config.EnableUpsells)
{
    ShowUpsells(config.MaxItems);
}

Grouped operational settings

Change retry behavior as one unit from the dashboard.

var policy = client.GetJson(
    "retry-policy",
    new RetryPolicy { MaxAttempts = 1, BackoffMs = 1000 });

httpClient.ConfigureRetries(policy.MaxAttempts, policy.BackoffMs);

UI remote config

Drive component visibility and copy from a single JSON flag.

const banner = flags.getJson<PromoBanner>("promo-banner", {
  visible: false,
  message: "",
});

if (banner.visible) {
  renderBanner(banner.message);
}

Environment-specific tuning

Ship the same code everywhere; tune the JSON object per environment in the dashboard without redeploying.

val config = client.getJson<CheckoutConfig>("checkout-config")
    ?: CheckoutConfig()

applyCheckoutLimits(config.maxItems)

Runtime behavior

  1. An operator changes a JSON flag value in the dashboard for a specific environment
  2. SOASAP Cloud pushes the update to connected SDKs over Server-Sent Events (SSE)
  3. The SDK validates the payload and atomically replaces its in-memory snapshot
  4. Subsequent getJson / GetJson calls deserialize or copy the new value

No redeploy is required. No network requests occur during evaluation — only during background synchronization. JavaScript and Python SDKs return a deep copy on each read; treat returned objects as snapshots, not mutable shared state.

See Real-Time Synchronization and Offline Operation.

Production recommendations

  • Use explicit defaults — pass a complete default object with safe values for every field.
  • Prefer strong typing — classes, interfaces, TypedDict, or @Serializable data classes instead of untyped dictionaries.
  • Keep payloads focused — one JSON flag per logical config domain; avoid monolithic objects that mix unrelated settings.
  • Do not store secrets — API keys, tokens, and credentials belong in secret managers, not in JSON flag values (especially client-side SDKs).
  • Use optional fields carefully — add new dashboard fields with safe defaults in code; unknown keys are typically ignored by deserializers.
  • Prefer JSON over String for structured data — do not serialize objects into String flags and parse manually on the hot path.

Common mistakes

Untyped dictionaries everywhere

Avoid:

var config = client.GetJson<Dictionary<string, object>>(
    "checkout-config",
    new Dictionary<string, object>());

var maxItems = (int)config["maxItems"]; // fragile

Prefer:

var config = client.GetJson(
    "checkout-config",
    new CheckoutConfig { EnableUpsells = false, MaxItems = 10 });

var maxItems = config.MaxItems;

Strong types catch schema drift at compile time and make code review and refactoring safer.

Incomplete default objects

A default like {} or null forces null-checks throughout the codebase and hides missing fields. Provide a fully populated default with conservative values for every property your application reads.

Storing secrets in JSON flags

JSON flag values sync to client-side SDKs and appear in browser bundles or mobile app storage. Never embed API keys, signing secrets, or private credentials in JSON flags. Use server-side SDKs with secret managers for sensitive configuration.

Splitting one object across many flags

Related fields such as checkout-max-items (Number) and checkout-enable-upsells (Boolean) can drift independently during dashboard edits. Group them in one checkout-config JSON flag when they change together.

Using JSON for a single scalar value

A JSON object with one field — { "enabled": true } — adds parsing overhead without benefit. Use a Boolean, Number, or String flag for single values.

Related documentation