json_to_toon 1.0.0
json_to_toon: ^1.0.0 copied to clipboard
A Flutter/Dart package for converting between JSON and TOON (Token-Oriented Object Notation) formats. TOON is a compact, human-readable encoding optimized for LLM prompts.
import 'package:flutter/material.dart';
import 'package:json_to_toon/json_to_toon.dart';
import 'dart:convert';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'JSON to TOON Converter',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
useMaterial3: true,
),
home: const ConverterPage(),
);
}
}
class ConverterPage extends StatefulWidget {
const ConverterPage({super.key});
@override
State<ConverterPage> createState() => _ConverterPageState();
}
class _ConverterPageState extends State<ConverterPage> {
final TextEditingController _inputController = TextEditingController();
final TextEditingController _outputController = TextEditingController();
final ToonConverter _converter = ToonConverter();
bool _isJsonToToon = true;
String _errorMessage = '';
@override
void initState() {
super.initState();
// Set example JSON
_inputController.text = '''{
"context": {
"task": "Our favorite hikes",
"location": "Boulder",
"season": "spring_2025"
},
"friends": ["ana", "luis", "sam"],
"hikes": [
{
"id": 1,
"name": "Blue Lake Trail",
"distanceKm": 7.5,
"elevationGain": 320,
"companion": "ana",
"wasSunny": true
},
{
"id": 2,
"name": "Ridge Overlook",
"distanceKm": 9.2,
"elevationGain": 540,
"companion": "luis",
"wasSunny": false
}
]
}''';
}
void _convert() {
setState(() {
_errorMessage = '';
try {
if (_isJsonToToon) {
// JSON to TOON
final toon = _converter.jsonToToon(_inputController.text);
_outputController.text = toon;
} else {
// TOON to JSON
final json = _converter.toonToJson(_inputController.text);
// Pretty print JSON
final decoded = jsonDecode(json);
final encoder = JsonEncoder.withIndent(' ');
_outputController.text = encoder.convert(decoded);
}
} catch (e) {
_errorMessage = 'Error: $e';
_outputController.text = '';
}
});
}
void _swap() {
setState(() {
_isJsonToToon = !_isJsonToToon;
// Swap input and output
final temp = _inputController.text;
_inputController.text = _outputController.text;
_outputController.text = temp;
_errorMessage = '';
});
}
void _clear() {
setState(() {
_inputController.clear();
_outputController.clear();
_errorMessage = '';
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
title: const Text('JSON ↔ TOON Converter'),
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
// Mode indicator
Card(
color: Theme.of(context).colorScheme.primaryContainer,
child: Padding(
padding: const EdgeInsets.all(12.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
_isJsonToToon ? 'JSON → TOON' : 'TOON → JSON',
style: Theme.of(context).textTheme.titleLarge?.copyWith(
color: Theme.of(context).colorScheme.onPrimaryContainer,
fontWeight: FontWeight.bold,
),
),
const SizedBox(width: 16),
IconButton(
icon: const Icon(Icons.swap_horiz),
onPressed: _swap,
tooltip: 'Swap direction',
),
],
),
),
),
const SizedBox(height: 16),
// Input field
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
_isJsonToToon ? 'JSON Input:' : 'TOON Input:',
style: Theme.of(context).textTheme.titleMedium,
),
const SizedBox(height: 8),
Expanded(
child: TextField(
controller: _inputController,
maxLines: null,
expands: true,
decoration: const InputDecoration(
border: OutlineInputBorder(),
hintText: 'Enter your data here...',
),
style: const TextStyle(fontFamily: 'monospace'),
),
),
],
),
),
const SizedBox(height: 16),
// Action buttons
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton.icon(
onPressed: _convert,
icon: const Icon(Icons.transform),
label: const Text('Convert'),
),
const SizedBox(width: 16),
OutlinedButton.icon(
onPressed: _clear,
icon: const Icon(Icons.clear),
label: const Text('Clear'),
),
],
),
const SizedBox(height: 16),
// Error message
if (_errorMessage.isNotEmpty)
Card(
color: Theme.of(context).colorScheme.errorContainer,
child: Padding(
padding: const EdgeInsets.all(12.0),
child: Text(
_errorMessage,
style: TextStyle(
color: Theme.of(context).colorScheme.onErrorContainer,
),
),
),
),
if (_errorMessage.isNotEmpty) const SizedBox(height: 16),
// Output field
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
_isJsonToToon ? 'TOON Output:' : 'JSON Output:',
style: Theme.of(context).textTheme.titleMedium,
),
const SizedBox(height: 8),
Expanded(
child: TextField(
controller: _outputController,
maxLines: null,
expands: true,
readOnly: true,
decoration: const InputDecoration(
border: OutlineInputBorder(),
hintText: 'Output will appear here...',
),
style: const TextStyle(fontFamily: 'monospace'),
),
),
],
),
),
],
),
),
);
}
@override
void dispose() {
_inputController.dispose();
_outputController.dispose();
super.dispose();
}
}