Skip to content

Dev-Beom/flutter-json-render

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

12 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

flutter_json_render

pub package pub points pub likes CI Release Publish Secret Scan license stars

A Flutter-first implementation of the vercel-labs/json-render concept.

flutter_json_render renders flat JSON specs into Flutter widgets through an explicit registry and catalog.

Quick links:

Highlights

  • Guardrailed component/action model
  • Flat and model-friendly spec shape (root + elements)
  • Dynamic prop resolution ($state, $item, $index, $cond)
  • Visibility and repeat support
  • Style presets and style-aware prompt generation
  • Custom style creation from JSON tokens
  • JSONL streaming patch compiler for progressive UI updates

Install

flutter pub add flutter_json_render

Table of Contents

Core Concepts

  • JsonCatalog: declares what components/actions are allowed
  • JsonRegistry: maps component type -> Flutter widget builder, action -> handler
  • JsonRenderer: renders JsonRenderSpec safely
  • JsonSpecStreamCompiler: compiles streamed JSONL specs/patches
  • JsonStyleDefinition: defines selectable style presets for generation/runtime

Quick Start

import 'package:flutter/material.dart';
import 'package:flutter_json_render/flutter_json_render.dart';

final catalog = JsonCatalog(
  components: {
    ...standardComponentDefinitions,
    'Panel': const JsonComponentDefinition(
      description: 'Simple card container',
      props: {
        'title': JsonPropDefinition(type: 'string', required: true),
      },
    ),
  },
  actions: {
    ...standardActionDefinitions,
    'increment': const JsonActionDefinition(description: 'Increase count'),
  },
  styles: {
    'clean': const JsonStyleDefinition(
      displayName: 'Clean',
      description: 'Neutral and productivity-focused',
      guidance: 'Use subtle borders and restrained color.',
    ),
    'midnight': const JsonStyleDefinition(
      displayName: 'Midnight',
      description: 'Dark, high-contrast dashboard style',
      guidance: 'Use compact spacing and bright accents.',
    ),
  },
);

final registry = defineRegistry(
  components: {
    ...standardComponentBuilders(),
    'Panel': (ctx) => Card(
      child: Padding(
        padding: const EdgeInsets.all(12),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Text(ctx.props['title']?.toString() ?? 'Panel'),
            const SizedBox(height: 8),
            ...ctx.children,
          ],
        ),
      ),
    ),
  },
  actions: {
    'increment': (ctx) {
      ctx.setStateModel((prev) {
        final next = <String, dynamic>{...prev};
        next['count'] = ((prev['count'] as num?) ?? 0) + 1;
        return next;
      });
    },
  },
);

final spec = JsonRenderSpec.fromJson({
  'root': 'panel',
  'state': {'count': 1},
  'elements': {
    'panel': {
      'type': 'Panel',
      'props': {'title': 'Counter'},
      'children': ['value', 'button'],
    },
    'value': {
      'type': 'Text',
      'props': {'text': {'$state': '/count'}},
    },
    'button': {
      'type': 'Button',
      'props': {'label': 'Increment'},
      'on': {
        'press': {'action': 'increment'}
      }
    }
  }
});

Widget app() {
  return MaterialApp(
    home: Scaffold(
      body: Center(
        child: JsonRenderer(
          spec: spec,
          registry: registry,
          styleId: 'clean', // runtime-selected style
        ),
      ),
    ),
  );
}

Generate style-aware LLM prompts:

final prompt = catalog.prompt(
  options: const JsonPromptOptions(
    selectedStyleId: 'clean',
    includeStyles: true,
  ),
);

Custom Styles

JsonStyleDefinition now supports JSON round-tripping and token maps:

final customStyle = JsonStyleDefinition.fromJson({
  'displayName': 'Aurora',
  'description': 'Cool dark surface with cyan accents',
  'guidance': 'Prefer high contrast and compact spacing.',
  'tokens': {
    'accent': '#22D3EE',
    'panelBackground': '#0B1220',
    'textPrimary': '#E0F2FE',
  },
});

final catalog = baseCatalog.withStyle('aurora', customStyle);

Bulk import style maps from API/LLM output:

final catalog = baseCatalog.withStylesFromJson({
  'aurora': {
    'displayName': 'Aurora',
    'description': 'Cool dark surface with cyan accents',
    'guidance': 'Prefer high contrast and compact spacing.',
    'tokens': {
      'accent': '#22D3EE',
      'panelBackground': '#0B1220',
      'textPrimary': '#E0F2FE',
    },
  },
});

See full style authoring guide:

Stream JSONL Patches

final compiler = JsonSpecStreamCompiler();

compiler.push('{"root":"root","elements":{"root":{"type":"Text","props":{"text":"loading"}}}}\n');
compiler.push('{"op":"replace","path":"/elements/root/props/text","value":"ready"}\n');

final spec = compiler.getResult();

Supported stream line shapes:

  • full spec object
  • single patch op object
  • patch op array
  • wrapped patch object: { "patch": [ ... ] }

Validate Specs

final result = validateSpec(spec, catalog: catalog, strictCatalog: false);
if (!result.isValid) {
  for (final issue in result.issues) {
    debugPrint('[${issue.severity}] ${issue.message}');
  }
}

Built-In Components

  • Text
  • Column
  • Row
  • Container
  • Center
  • SizedBox
  • Button
  • Image

Row overflow strategy:

  • overflow: "row" (default): normal Row
  • overflow: "wrap": uses Wrap to avoid horizontal overflow
  • overflow: "scroll": horizontal scroll container

Example:

{
  "type": "Row",
  "props": {
    "spacing": 8,
    "runSpacing": 8,
    "overflow": "wrap"
  },
  "children": ["a", "b", "c"]
}

Example App

A full multi-scenario showcase app is included in /example:

cd example
flutter run

Included scenarios:

  • Counter + visibility conditions
  • Repeat + $item/$index actions
  • Dynamic props via $cond
  • Async action flow
  • Streamed JSONL patch simulation
  • Chat-like stream build-up
  • Streamed multi-component build-up (Text, Row, Column, Container, Center, SizedBox, Button, custom components)

The example includes a Style Preset dropdown and supports startup style selection:

cd example
flutter run --dart-define=STYLE_PRESET=midnight

You can also add a custom style at runtime:

  • Click Add Custom Style and paste a style JSON object.
  • Or boot with CUSTOM_STYLE_JSON:
cd example
flutter run \
  --dart-define=STYLE_PRESET=sunset \
  --dart-define=CUSTOM_STYLE_JSON='{"id":"aurora","base":"midnight","displayName":"Aurora","description":"Deep blue surface with bright cyan accents.","guidance":"Use high contrast and cool accent colors.","tokens":{"accent":"#22D3EE","panelBackground":"#0B1220","textPrimary":"#E0F2FE"}}'

Style Preset Screenshots

clean midnight sunset
clean midnight sunset

Streaming LLM Output Demo (GIF)

JSONL patch stream progressively builds a component-rich interface:

stream-demo

Replay the same capture scenario locally:

cd example
flutter run \
  --dart-define=STYLE_PRESET=sunset \
  --dart-define=SCENARIO=component_stream \
  --dart-define=AUTO_RUN_STREAM=true \
  --dart-define=AUTO_RUN_DELAY_MS=2200 \
  --dart-define=STREAM_STEP_DELAY_MS=620 \
  --dart-define=CAPTURE_MODE=true

pub.dev Release Checklist

flutter analyze
flutter test
dart pub publish --dry-run

Security Scanning

GitHub Actions secret scanning is enabled with both gitleaks and trufflehog.

  • Workflow: .github/workflows/secret-scan.yml
  • Runs on pull requests, pushes to main, weekly schedule, and manual dispatch

License

MIT