lots of progress
This commit is contained in:
@@ -0,0 +1,183 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Http\JsonResponse;
|
||||||
|
use Illuminate\Support\Facades\Validator;
|
||||||
|
|
||||||
|
class JsonApiController extends Controller
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Validate JSON data
|
||||||
|
*
|
||||||
|
* @param Request $request
|
||||||
|
* @return JsonResponse
|
||||||
|
*/
|
||||||
|
public function validateJson(Request $request): JsonResponse
|
||||||
|
{
|
||||||
|
$validator = Validator::make($request->all(), [
|
||||||
|
'json' => 'required|json',
|
||||||
|
]);
|
||||||
|
|
||||||
|
if ($validator->fails()) {
|
||||||
|
return response()->json([
|
||||||
|
'status' => 'error',
|
||||||
|
'message' => 'Invalid request',
|
||||||
|
'errors' => $validator->errors()
|
||||||
|
], 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$json = json_decode($request->input('json'), true, 512, JSON_THROW_ON_ERROR);
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'status' => 'success',
|
||||||
|
'message' => 'Valid JSON',
|
||||||
|
'data' => $json
|
||||||
|
]);
|
||||||
|
} catch (\JsonException $e) {
|
||||||
|
return response()->json([
|
||||||
|
'status' => 'error',
|
||||||
|
'message' => 'Invalid JSON',
|
||||||
|
'error' => $e->getMessage()
|
||||||
|
], 400);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate JSON against a schema
|
||||||
|
*
|
||||||
|
* @param Request $request
|
||||||
|
* @return JsonResponse
|
||||||
|
*/
|
||||||
|
public function validateSchema(Request $request): JsonResponse
|
||||||
|
{
|
||||||
|
$validator = Validator::make($request->all(), [
|
||||||
|
'json' => 'required|json',
|
||||||
|
'schema' => 'required|json',
|
||||||
|
]);
|
||||||
|
|
||||||
|
if ($validator->fails()) {
|
||||||
|
return response()->json([
|
||||||
|
'status' => 'error',
|
||||||
|
'message' => 'Invalid request',
|
||||||
|
'errors' => $validator->errors()
|
||||||
|
], 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$json = json_decode($request->input('json'), true, 512, JSON_THROW_ON_ERROR);
|
||||||
|
$schema = json_decode($request->input('schema'), true, 512, JSON_THROW_ON_ERROR);
|
||||||
|
|
||||||
|
// Use Ajv for schema validation
|
||||||
|
$ajv = new \Ajv();
|
||||||
|
$validate = $ajv->compile($schema);
|
||||||
|
$valid = $validate($json);
|
||||||
|
|
||||||
|
if ($valid) {
|
||||||
|
return response()->json([
|
||||||
|
'status' => 'success',
|
||||||
|
'message' => 'JSON is valid according to the schema',
|
||||||
|
'data' => $json
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'status' => 'error',
|
||||||
|
'message' => 'JSON does not match schema',
|
||||||
|
'errors' => $validate->errors
|
||||||
|
], 400);
|
||||||
|
|
||||||
|
} catch (\JsonException $e) {
|
||||||
|
return response()->json([
|
||||||
|
'status' => 'error',
|
||||||
|
'message' => 'Invalid JSON or schema',
|
||||||
|
'error' => $e->getMessage()
|
||||||
|
], 400);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format JSON
|
||||||
|
*
|
||||||
|
* @param Request $request
|
||||||
|
* @return JsonResponse
|
||||||
|
*/
|
||||||
|
public function formatJson(Request $request): JsonResponse
|
||||||
|
{
|
||||||
|
$validator = Validator::make($request->all(), [
|
||||||
|
'json' => 'required|json',
|
||||||
|
'indent' => 'integer|min:0|max:8',
|
||||||
|
]);
|
||||||
|
|
||||||
|
if ($validator->fails()) {
|
||||||
|
return response()->json([
|
||||||
|
'status' => 'error',
|
||||||
|
'message' => 'Invalid request',
|
||||||
|
'errors' => $validator->errors()
|
||||||
|
], 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$json = json_decode($request->input('json'), true, 512, JSON_THROW_ON_ERROR);
|
||||||
|
$indent = $request->input('indent', 2);
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'status' => 'success',
|
||||||
|
'message' => 'JSON formatted successfully',
|
||||||
|
'data' => json_encode($json, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE, $indent)
|
||||||
|
]);
|
||||||
|
} catch (\JsonException $e) {
|
||||||
|
return response()->json([
|
||||||
|
'status' => 'error',
|
||||||
|
'message' => 'Invalid JSON',
|
||||||
|
'error' => $e->getMessage()
|
||||||
|
], 400);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compare two JSON documents
|
||||||
|
*
|
||||||
|
* @param Request $request
|
||||||
|
* @return JsonResponse
|
||||||
|
*/
|
||||||
|
public function compareJson(Request $request): JsonResponse
|
||||||
|
{
|
||||||
|
$validator = Validator::make($request->all(), [
|
||||||
|
'json1' => 'required|json',
|
||||||
|
'json2' => 'required|json',
|
||||||
|
]);
|
||||||
|
|
||||||
|
if ($validator->fails()) {
|
||||||
|
return response()->json([
|
||||||
|
'status' => 'error',
|
||||||
|
'message' => 'Invalid request',
|
||||||
|
'errors' => $validator->errors()
|
||||||
|
], 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$json1 = json_decode($request->input('json1'), true, 512, JSON_THROW_ON_ERROR);
|
||||||
|
$json2 = json_decode($request->input('json2'), true, 512, JSON_THROW_ON_ERROR);
|
||||||
|
|
||||||
|
$diff = new \Diff($json1, $json2);
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'status' => 'success',
|
||||||
|
'message' => 'JSON comparison completed',
|
||||||
|
'data' => [
|
||||||
|
'are_equal' => $json1 === $json2,
|
||||||
|
'differences' => $diff->getDifferences()
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
} catch (\JsonException $e) {
|
||||||
|
return response()->json([
|
||||||
|
'status' => 'error',
|
||||||
|
'message' => 'Invalid JSON',
|
||||||
|
'error' => $e->getMessage()
|
||||||
|
], 400);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+1
-1
@@ -76,4 +76,4 @@
|
|||||||
},
|
},
|
||||||
"minimum-stability": "stable",
|
"minimum-stability": "stable",
|
||||||
"prefer-stable": true
|
"prefer-stable": true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,55 @@
|
|||||||
|
services:
|
||||||
|
laravel.test:
|
||||||
|
build:
|
||||||
|
context: './vendor/laravel/sail/runtimes/8.4'
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
args:
|
||||||
|
WWWGROUP: '${WWWGROUP}'
|
||||||
|
image: 'sail-8.4/app'
|
||||||
|
extra_hosts:
|
||||||
|
- 'host.docker.internal:host-gateway'
|
||||||
|
ports:
|
||||||
|
- '${APP_PORT:-8082}:80'
|
||||||
|
- '${VITE_PORT:-5173}:${VITE_PORT:-5173}'
|
||||||
|
environment:
|
||||||
|
WWWUSER: '${WWWUSER}'
|
||||||
|
LARAVEL_SAIL: 1
|
||||||
|
XDEBUG_MODE: '${SAIL_XDEBUG_MODE:-off}'
|
||||||
|
XDEBUG_CONFIG: '${SAIL_XDEBUG_CONFIG:-client_host=host.docker.internal}'
|
||||||
|
IGNITION_LOCAL_SITES_PATH: '${PWD}'
|
||||||
|
volumes:
|
||||||
|
- '.:/var/www/html'
|
||||||
|
networks:
|
||||||
|
- sail
|
||||||
|
depends_on:
|
||||||
|
- mysql
|
||||||
|
mysql:
|
||||||
|
image: 'mysql/mysql-server:8.0'
|
||||||
|
ports:
|
||||||
|
- '${FORWARD_DB_PORT:-3309}:3306'
|
||||||
|
environment:
|
||||||
|
MYSQL_ROOT_PASSWORD: '${DB_PASSWORD}'
|
||||||
|
MYSQL_ROOT_HOST: '%'
|
||||||
|
MYSQL_DATABASE: '${DB_DATABASE}'
|
||||||
|
MYSQL_USER: '${DB_USERNAME}'
|
||||||
|
MYSQL_PASSWORD: '${DB_PASSWORD}'
|
||||||
|
MYSQL_ALLOW_EMPTY_PASSWORD: 1
|
||||||
|
volumes:
|
||||||
|
- 'sail-mysql:/var/lib/mysql'
|
||||||
|
- './vendor/laravel/sail/database/mysql/create-testing-database.sh:/docker-entrypoint-initdb.d/10-create-testing-database.sh'
|
||||||
|
networks:
|
||||||
|
- sail
|
||||||
|
healthcheck:
|
||||||
|
test:
|
||||||
|
- CMD
|
||||||
|
- mysqladmin
|
||||||
|
- ping
|
||||||
|
- '-p${DB_PASSWORD}'
|
||||||
|
retries: 3
|
||||||
|
timeout: 5s
|
||||||
|
networks:
|
||||||
|
sail:
|
||||||
|
driver: bridge
|
||||||
|
volumes:
|
||||||
|
sail-mysql:
|
||||||
|
driver: local
|
||||||
@@ -22,7 +22,6 @@
|
|||||||
<env name="APP_MAINTENANCE_DRIVER" value="file"/>
|
<env name="APP_MAINTENANCE_DRIVER" value="file"/>
|
||||||
<env name="BCRYPT_ROUNDS" value="4"/>
|
<env name="BCRYPT_ROUNDS" value="4"/>
|
||||||
<env name="CACHE_STORE" value="array"/>
|
<env name="CACHE_STORE" value="array"/>
|
||||||
<env name="DB_CONNECTION" value="sqlite"/>
|
|
||||||
<env name="DB_DATABASE" value=":memory:"/>
|
<env name="DB_DATABASE" value=":memory:"/>
|
||||||
<env name="MAIL_MAILER" value="array"/>
|
<env name="MAIL_MAILER" value="array"/>
|
||||||
<env name="PULSE_ENABLED" value="false"/>
|
<env name="PULSE_ENABLED" value="false"/>
|
||||||
|
|||||||
@@ -0,0 +1,291 @@
|
|||||||
|
@extends('layouts.app')
|
||||||
|
|
||||||
|
@section('title', 'JSON API Documentation')
|
||||||
|
|
||||||
|
@push('styles')
|
||||||
|
<style>
|
||||||
|
.endpoint-card {
|
||||||
|
border: 1px solid var(--bs-border-color);
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.endpoint-card:hover {
|
||||||
|
box-shadow: 0 6px 12px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.endpoint-header {
|
||||||
|
background-color: var(--bs-body-bg);
|
||||||
|
border-bottom: 1px solid var(--bs-border-color);
|
||||||
|
padding: 1rem;
|
||||||
|
border-radius: 0.5rem 0.5rem 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.endpoint-method {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 0.25rem 0.5rem;
|
||||||
|
border-radius: 0.25rem;
|
||||||
|
font-weight: bold;
|
||||||
|
margin-right: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.method-post {
|
||||||
|
background-color: #49cc90;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.endpoint-path {
|
||||||
|
font-family: 'Courier New', monospace;
|
||||||
|
font-size: 1.1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.endpoint-body {
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.request-example {
|
||||||
|
background-color: var(--bs-body-bg);
|
||||||
|
border: 1px solid var(--bs-border-color);
|
||||||
|
border-radius: 0.375rem;
|
||||||
|
padding: 1rem;
|
||||||
|
margin: 1rem 0;
|
||||||
|
font-family: 'Courier New', monospace;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.response-example {
|
||||||
|
background-color: var(--bs-body-bg);
|
||||||
|
border: 1px solid var(--bs-border-color);
|
||||||
|
border-radius: 0.375rem;
|
||||||
|
padding: 1rem;
|
||||||
|
margin: 1rem 0;
|
||||||
|
font-family: 'Courier New', monospace;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-section {
|
||||||
|
background: linear-gradient(135deg, var(--primary-color) 0%, #6a8eff 100%);
|
||||||
|
color: white;
|
||||||
|
padding: 3rem 0;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.code-block {
|
||||||
|
background-color: var(--bs-body-bg);
|
||||||
|
border: 1px solid var(--bs-border-color);
|
||||||
|
border-radius: 0.375rem;
|
||||||
|
padding: 1rem;
|
||||||
|
margin: 1rem 0;
|
||||||
|
font-family: 'Courier New', monospace;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
@endpush
|
||||||
|
|
||||||
|
@section('content')
|
||||||
|
<div class="hero-section text-center">
|
||||||
|
<h1 class="display-4 fw-bold mb-3">JSON API Documentation</h1>
|
||||||
|
<p class="lead mb-4">Programmatic access to JSON validation and formatting tools</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card shadow-sm mb-4">
|
||||||
|
<div class="card-body">
|
||||||
|
<h2 class="h4 mb-3">Getting Started</h2>
|
||||||
|
<p>Our JSON API provides endpoints for validating, formatting, and comparing JSON documents. All endpoints accept and return JSON data.</p>
|
||||||
|
|
||||||
|
<h3 class="h5 mt-4">Base URL</h3>
|
||||||
|
<div class="code-block">
|
||||||
|
{{ url('/api/json') }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3 class="h5 mt-4">Authentication</h3>
|
||||||
|
<p>Currently, the API is open and does not require authentication. However, rate limiting may be implemented in the future.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="endpoint-card">
|
||||||
|
<div class="endpoint-header">
|
||||||
|
<span class="endpoint-method method-post">POST</span>
|
||||||
|
<span class="endpoint-path">/validate</span>
|
||||||
|
</div>
|
||||||
|
<div class="endpoint-body">
|
||||||
|
<h3 class="h5">Validate JSON</h3>
|
||||||
|
<p>Validates a JSON document and returns the parsed data if valid.</p>
|
||||||
|
|
||||||
|
<h4 class="h6 mt-3">Request</h4>
|
||||||
|
<div class="request-example">
|
||||||
|
<pre><code>{
|
||||||
|
"json": "{\"name\":\"John\",\"age\":30}"
|
||||||
|
}</code></pre>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h4 class="h6 mt-3">Response (Success)</h4>
|
||||||
|
<div class="response-example">
|
||||||
|
<pre><code>{
|
||||||
|
"status": "success",
|
||||||
|
"message": "Valid JSON",
|
||||||
|
"data": {
|
||||||
|
"name": "John",
|
||||||
|
"age": 30
|
||||||
|
}
|
||||||
|
}</code></pre>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h4 class="h6 mt-3">Response (Error)</h4>
|
||||||
|
<div class="response-example">
|
||||||
|
<pre><code>{
|
||||||
|
"status": "error",
|
||||||
|
"message": "Invalid JSON",
|
||||||
|
"error": "Syntax error"
|
||||||
|
}</code></pre>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="endpoint-card">
|
||||||
|
<div class="endpoint-header">
|
||||||
|
<span class="endpoint-method method-post">POST</span>
|
||||||
|
<span class="endpoint-path">/validate-schema</span>
|
||||||
|
</div>
|
||||||
|
<div class="endpoint-body">
|
||||||
|
<h3 class="h5">Validate JSON Against Schema</h3>
|
||||||
|
<p>Validates a JSON document against a JSON Schema.</p>
|
||||||
|
|
||||||
|
<h4 class="h6 mt-3">Request</h4>
|
||||||
|
<div class="request-example">
|
||||||
|
<pre><code>{
|
||||||
|
"json": "{\"name\":\"John\",\"age\":30}",
|
||||||
|
"schema": "{
|
||||||
|
\"type\":\"object\",
|
||||||
|
\"properties\":{
|
||||||
|
\"name\":{\"type\":\"string\"},
|
||||||
|
\"age\":{\"type\":\"number\"}
|
||||||
|
},
|
||||||
|
\"required\":[\"name\",\"age\"]
|
||||||
|
}"
|
||||||
|
}</code></pre>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h4 class="h6 mt-3">Response (Success)</h4>
|
||||||
|
<div class="response-example">
|
||||||
|
<pre><code>{
|
||||||
|
"status": "success",
|
||||||
|
"message": "JSON is valid according to the schema",
|
||||||
|
"data": {
|
||||||
|
"name": "John",
|
||||||
|
"age": 30
|
||||||
|
}
|
||||||
|
}</code></pre>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h4 class="h6 mt-3">Response (Error)</h4>
|
||||||
|
<div class="response-example">
|
||||||
|
<pre><code>{
|
||||||
|
"status": "error",
|
||||||
|
"message": "JSON does not match schema",
|
||||||
|
"errors": [
|
||||||
|
{
|
||||||
|
"instancePath": "/age",
|
||||||
|
"message": "must be a number"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}</code></pre>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="endpoint-card">
|
||||||
|
<div class="endpoint-header">
|
||||||
|
<span class="endpoint-method method-post">POST</span>
|
||||||
|
<span class="endpoint-path">/format</span>
|
||||||
|
</div>
|
||||||
|
<div class="endpoint-body">
|
||||||
|
<h3 class="h5">Format JSON</h3>
|
||||||
|
<p>Formats a JSON document with specified indentation.</p>
|
||||||
|
|
||||||
|
<h4 class="h6 mt-3">Request</h4>
|
||||||
|
<div class="request-example">
|
||||||
|
<pre><code>{
|
||||||
|
"json": "{\"name\":\"John\",\"age\":30}",
|
||||||
|
"indent": 2
|
||||||
|
}</code></pre>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h4 class="h6 mt-3">Response (Success)</h4>
|
||||||
|
<div class="response-example">
|
||||||
|
<pre><code>{
|
||||||
|
"status": "success",
|
||||||
|
"message": "JSON formatted successfully",
|
||||||
|
"data": "{
|
||||||
|
\"name\": \"John\",
|
||||||
|
\"age\": 30
|
||||||
|
}"
|
||||||
|
}</code></pre>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="endpoint-card">
|
||||||
|
<div class="endpoint-header">
|
||||||
|
<span class="endpoint-method method-post">POST</span>
|
||||||
|
<span class="endpoint-path">/compare</span>
|
||||||
|
</div>
|
||||||
|
<div class="endpoint-body">
|
||||||
|
<h3 class="h5">Compare JSON Documents</h3>
|
||||||
|
<p>Compares two JSON documents and returns the differences.</p>
|
||||||
|
|
||||||
|
<h4 class="h6 mt-3">Request</h4>
|
||||||
|
<div class="request-example">
|
||||||
|
<pre><code>{
|
||||||
|
"json1": "{\"name\":\"John\",\"age\":30}",
|
||||||
|
"json2": "{\"name\":\"John\",\"age\":31}"
|
||||||
|
}</code></pre>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h4 class="h6 mt-3">Response (Success)</h4>
|
||||||
|
<div class="response-example">
|
||||||
|
<pre><code>{
|
||||||
|
"status": "success",
|
||||||
|
"message": "JSON comparison completed",
|
||||||
|
"data": {
|
||||||
|
"are_equal": false,
|
||||||
|
"differences": [
|
||||||
|
{
|
||||||
|
"path": ["age"],
|
||||||
|
"old_value": 30,
|
||||||
|
"new_value": 31
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}</code></pre>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card shadow-sm mt-4">
|
||||||
|
<div class="card-body">
|
||||||
|
<h2 class="h4 mb-3">Example Usage</h2>
|
||||||
|
<p>Here's an example of how to use the API with cURL:</p>
|
||||||
|
|
||||||
|
<div class="code-block">
|
||||||
|
<pre><code># Validate JSON
|
||||||
|
curl -X POST {{ url('/api/json/validate') }} \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{"json":"{\"name\":\"John\",\"age\":30}"}'
|
||||||
|
|
||||||
|
# Format JSON
|
||||||
|
curl -X POST {{ url('/api/json/format') }} \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{"json":"{\"name\":\"John\",\"age\":30}","indent":2}'
|
||||||
|
|
||||||
|
# Compare JSON
|
||||||
|
curl -X POST {{ url('/api/json/compare') }} \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{"json1":"{\"name\":\"John\",\"age\":30}","json2":"{\"name\":\"John\",\"age\":31}"}'</code></pre>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endsection
|
||||||
@@ -0,0 +1,251 @@
|
|||||||
|
@extends('layouts.app')
|
||||||
|
|
||||||
|
@section('title', 'JSON Compare - Free Online JSON Diff Tool')
|
||||||
|
|
||||||
|
@push('styles')
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/jsondiffpatch@0.4.1/dist/formatters-styles/html.css" rel="stylesheet">
|
||||||
|
<style>
|
||||||
|
.json-input {
|
||||||
|
min-height: 300px;
|
||||||
|
font-family: 'Courier New', monospace;
|
||||||
|
background-color: var(--bs-body-bg);
|
||||||
|
border: 1px solid var(--bs-border-color);
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.json-input:focus {
|
||||||
|
border-color: var(--primary-color);
|
||||||
|
box-shadow: 0 0 0 0.25rem rgba(74, 107, 255, 0.25);
|
||||||
|
}
|
||||||
|
|
||||||
|
.result-box {
|
||||||
|
display: none;
|
||||||
|
animation: fadeIn 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fadeIn {
|
||||||
|
from { opacity: 0; transform: translateY(-10px); }
|
||||||
|
to { opacity: 1; transform: translateY(0); }
|
||||||
|
}
|
||||||
|
|
||||||
|
.result-box.show {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.diff-viewer {
|
||||||
|
height: 500px;
|
||||||
|
border: 1px solid var(--bs-border-color);
|
||||||
|
border-radius: 0.375rem;
|
||||||
|
margin-top: 1rem;
|
||||||
|
background-color: var(--bs-body-bg);
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-section {
|
||||||
|
background: linear-gradient(135deg, var(--primary-color) 0%, #6a8eff 100%);
|
||||||
|
color: white;
|
||||||
|
padding: 3rem 0;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.feature-icon {
|
||||||
|
font-size: 2rem;
|
||||||
|
color: var(--primary-color);
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.feature-card {
|
||||||
|
text-align: center;
|
||||||
|
padding: 1.5rem;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
background-color: var(--bs-body-bg);
|
||||||
|
border: 1px solid var(--bs-border-color);
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.feature-card:hover {
|
||||||
|
transform: translateY(-5px);
|
||||||
|
box-shadow: 0 6px 12px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Dark mode styles for jsondiffpatch */
|
||||||
|
[data-bs-theme="dark"] .jsondiffpatch-delta {
|
||||||
|
background-color: var(--bs-dark);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-bs-theme="dark"] .jsondiffpatch-delta pre {
|
||||||
|
color: var(--bs-light);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-bs-theme="dark"] .jsondiffpatch-added {
|
||||||
|
background-color: rgba(40, 167, 69, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-bs-theme="dark"] .jsondiffpatch-removed {
|
||||||
|
background-color: rgba(220, 53, 69, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-bs-theme="dark"] .jsondiffpatch-modified {
|
||||||
|
background-color: rgba(255, 193, 7, 0.2);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
@endpush
|
||||||
|
|
||||||
|
@section('content')
|
||||||
|
<div class="hero-section text-center">
|
||||||
|
<h1 class="display-4 fw-bold mb-3">JSON Compare Tool</h1>
|
||||||
|
<p class="lead mb-4">Compare and visualize differences between two JSON documents</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row mb-4">
|
||||||
|
<div class="col-md-4 mb-3">
|
||||||
|
<div class="feature-card">
|
||||||
|
<i class="bi bi-arrows-angle-contract feature-icon"></i>
|
||||||
|
<h3>Side-by-Side Diff</h3>
|
||||||
|
<p>View differences between two JSON documents in a clear, visual format</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4 mb-3">
|
||||||
|
<div class="feature-card">
|
||||||
|
<i class="bi bi-code-slash feature-icon"></i>
|
||||||
|
<h3>Color-Coded</h3>
|
||||||
|
<p>Easily identify additions, deletions, and modifications with color coding</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4 mb-3">
|
||||||
|
<div class="feature-card">
|
||||||
|
<i class="bi bi-arrows-collapse feature-icon"></i>
|
||||||
|
<h3>Collapsible</h3>
|
||||||
|
<p>Navigate large JSON documents with collapsible nodes</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card shadow-sm">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6 mb-3">
|
||||||
|
<label for="jsonInput1" class="form-label">First JSON:</label>
|
||||||
|
<textarea class="form-control json-input" id="jsonInput1" rows="10" placeholder='{
|
||||||
|
"example": "json",
|
||||||
|
"format": "here"
|
||||||
|
}'></textarea>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6 mb-3">
|
||||||
|
<label for="jsonInput2" class="form-label">Second JSON:</label>
|
||||||
|
<textarea class="form-control json-input" id="jsonInput2" rows="10" placeholder='{
|
||||||
|
"example": "json",
|
||||||
|
"format": "here"
|
||||||
|
}'></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="d-flex gap-2">
|
||||||
|
<button class="btn btn-primary" id="compareBtn">
|
||||||
|
<i class="bi bi-arrows-angle-contract me-2"></i>Compare JSON
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-outline-secondary" id="clearBtn">
|
||||||
|
<i class="bi bi-trash me-2"></i>Clear
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card shadow-sm mt-4 result-box" id="resultBox">
|
||||||
|
<div class="card-body">
|
||||||
|
<h3 class="h5 card-title">Comparison Result</h3>
|
||||||
|
<div id="resultContent"></div>
|
||||||
|
<div class="diff-viewer" id="diffViewer"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-5">
|
||||||
|
<h2 class="h4 mb-3">About JSON Compare Tool</h2>
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-body">
|
||||||
|
<p>Our JSON Compare Tool helps you identify differences between two JSON documents quickly and efficiently. Whether you're comparing API responses, configuration files, or any other JSON data, our tool provides a clear visualization of the changes.</p>
|
||||||
|
|
||||||
|
<h3 class="h5 mt-4">Key Features:</h3>
|
||||||
|
<ul>
|
||||||
|
<li>Side-by-side JSON comparison</li>
|
||||||
|
<li>Color-coded differences (green for additions, red for deletions, yellow for modifications)</li>
|
||||||
|
<li>Collapsible nodes for easy navigation</li>
|
||||||
|
<li>Dark and light mode support</li>
|
||||||
|
<li>Mobile-friendly interface</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h3 class="h5 mt-4">How to Use:</h3>
|
||||||
|
<ol>
|
||||||
|
<li>Enter or paste your first JSON document in the left textarea</li>
|
||||||
|
<li>Enter or paste your second JSON document in the right textarea</li>
|
||||||
|
<li>Click the "Compare JSON" button to see the differences</li>
|
||||||
|
<li>Use the collapsible nodes to navigate through the differences</li>
|
||||||
|
</ol>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endsection
|
||||||
|
|
||||||
|
@push('scripts')
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/jsondiffpatch@0.4.1/dist/jsondiffpatch.umd.js"></script>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/jsondiffpatch@0.4.1/dist/formatters-styles/html.js"></script>
|
||||||
|
<script>
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
const compareBtn = document.getElementById('compareBtn');
|
||||||
|
const clearBtn = document.getElementById('clearBtn');
|
||||||
|
const jsonInput1 = document.getElementById('jsonInput1');
|
||||||
|
const jsonInput2 = document.getElementById('jsonInput2');
|
||||||
|
const resultBox = document.getElementById('resultBox');
|
||||||
|
const resultContent = document.getElementById('resultContent');
|
||||||
|
const diffViewer = document.getElementById('diffViewer');
|
||||||
|
|
||||||
|
// Compare JSON
|
||||||
|
compareBtn.addEventListener('click', function() {
|
||||||
|
try {
|
||||||
|
const json1 = JSON.parse(jsonInput1.value);
|
||||||
|
const json2 = JSON.parse(jsonInput2.value);
|
||||||
|
|
||||||
|
const delta = jsondiffpatch.diff(json1, json2);
|
||||||
|
|
||||||
|
if (!delta) {
|
||||||
|
resultContent.innerHTML = `
|
||||||
|
<div class="alert alert-success">
|
||||||
|
<i class="bi bi-check-circle-fill me-2"></i>
|
||||||
|
The JSON documents are identical!
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
diffViewer.innerHTML = '';
|
||||||
|
} else {
|
||||||
|
resultContent.innerHTML = `
|
||||||
|
<div class="alert alert-info">
|
||||||
|
<i class="bi bi-info-circle-fill me-2"></i>
|
||||||
|
Differences found between the JSON documents
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
diffViewer.innerHTML = jsondiffpatch.formatters.html.format(delta, json1);
|
||||||
|
}
|
||||||
|
|
||||||
|
resultBox.classList.add('show');
|
||||||
|
} catch (e) {
|
||||||
|
resultContent.innerHTML = `
|
||||||
|
<div class="alert alert-danger">
|
||||||
|
<i class="bi bi-exclamation-circle-fill me-2"></i>
|
||||||
|
Error comparing JSON: ${e.message}
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
resultBox.classList.add('show');
|
||||||
|
diffViewer.innerHTML = '';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Clear input
|
||||||
|
clearBtn.addEventListener('click', function() {
|
||||||
|
jsonInput1.value = '';
|
||||||
|
jsonInput2.value = '';
|
||||||
|
resultBox.classList.remove('show');
|
||||||
|
diffViewer.innerHTML = '';
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
@endpush
|
||||||
@@ -0,0 +1,577 @@
|
|||||||
|
@extends('layouts.app')
|
||||||
|
|
||||||
|
@section('title', 'JSON Lint - Free Online JSON Validator and Formatter')
|
||||||
|
|
||||||
|
@push('styles')
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.0/font/bootstrap-icons.css" rel="stylesheet">
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/jsoneditor@9.10.2/dist/jsoneditor.min.css" rel="stylesheet">
|
||||||
|
<style>
|
||||||
|
:root {
|
||||||
|
--primary-color: #4a6bff;
|
||||||
|
--primary-hover: #3a5bef;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
background-color: var(--bs-body-bg);
|
||||||
|
transition: background-color 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.json-input {
|
||||||
|
min-height: 300px;
|
||||||
|
font-family: 'Courier New', monospace;
|
||||||
|
background-color: var(--bs-body-bg);
|
||||||
|
border: 1px solid var(--bs-border-color);
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.json-input:focus {
|
||||||
|
border-color: var(--primary-color);
|
||||||
|
box-shadow: 0 0 0 0.25rem rgba(74, 107, 255, 0.25);
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-switch {
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 0.5rem;
|
||||||
|
border-radius: 50%;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
background-color: var(--bs-body-bg);
|
||||||
|
border: 1px solid var(--bs-border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-switch:hover {
|
||||||
|
background-color: var(--bs-border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.result-box {
|
||||||
|
display: none;
|
||||||
|
animation: fadeIn 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fadeIn {
|
||||||
|
from { opacity: 0; transform: translateY(-10px); }
|
||||||
|
to { opacity: 1; transform: translateY(0); }
|
||||||
|
}
|
||||||
|
|
||||||
|
.result-box.show {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tree-view-container {
|
||||||
|
height: 500px;
|
||||||
|
border: 1px solid var(--bs-border-color);
|
||||||
|
border-radius: 0.375rem;
|
||||||
|
margin-top: 1rem;
|
||||||
|
background-color: var(--bs-body-bg);
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Tree view animations */
|
||||||
|
.jsoneditor-tree {
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.jsoneditor-tree .jsoneditor-expandable {
|
||||||
|
transition: transform 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.jsoneditor-tree .jsoneditor-expandable:hover {
|
||||||
|
transform: scale(1.02);
|
||||||
|
}
|
||||||
|
|
||||||
|
.jsoneditor-tree .jsoneditor-field {
|
||||||
|
transition: color 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.jsoneditor-tree .jsoneditor-field:hover {
|
||||||
|
color: var(--primary-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.jsoneditor-tree .jsoneditor-value {
|
||||||
|
transition: color 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.jsoneditor-tree .jsoneditor-value:hover {
|
||||||
|
color: var(--primary-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.jsoneditor-tree .jsoneditor-separator {
|
||||||
|
transition: opacity 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.jsoneditor-tree .jsoneditor-separator:hover {
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Collapsible node styles */
|
||||||
|
.jsoneditor-tree .jsoneditor-navigation-bar {
|
||||||
|
padding: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.jsoneditor-tree .jsoneditor-navigation-bar button {
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.jsoneditor-tree .jsoneditor-navigation-bar button:hover {
|
||||||
|
background-color: var(--bs-border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Smooth collapse/expand animation */
|
||||||
|
.jsoneditor-tree .jsoneditor-expandable {
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.jsoneditor-tree .jsoneditor-expandable.collapsed {
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.jsoneditor-tree .jsoneditor-expandable.expanded {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-tabs {
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
border-bottom: 1px solid var(--bs-border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-tabs .nav-link {
|
||||||
|
color: var(--bs-body-color);
|
||||||
|
border: none;
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-tabs .nav-link.active {
|
||||||
|
color: var(--primary-color);
|
||||||
|
border-bottom: 2px solid var(--primary-color);
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-tabs .nav-link:hover {
|
||||||
|
color: var(--primary-color);
|
||||||
|
border-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.jsoneditor {
|
||||||
|
border: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.jsoneditor-menu {
|
||||||
|
background-color: var(--bs-body-bg) !important;
|
||||||
|
border-bottom: 1px solid var(--bs-border-color) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.jsoneditor-navigation-bar {
|
||||||
|
background-color: var(--bs-body-bg) !important;
|
||||||
|
border-bottom: 1px solid var(--bs-border-color) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary {
|
||||||
|
background-color: var(--primary-color);
|
||||||
|
border-color: var(--primary-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary:hover {
|
||||||
|
background-color: var(--primary-hover);
|
||||||
|
border-color: var(--primary-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
.card {
|
||||||
|
border: none;
|
||||||
|
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card:hover {
|
||||||
|
box-shadow: 0 6px 12px rgba(0, 0, 0, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-section {
|
||||||
|
background: linear-gradient(135deg, var(--primary-color) 0%, #6a8eff 100%);
|
||||||
|
color: white;
|
||||||
|
padding: 3rem 0;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.feature-icon {
|
||||||
|
font-size: 2rem;
|
||||||
|
color: var(--primary-color);
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.feature-card {
|
||||||
|
text-align: center;
|
||||||
|
padding: 1.5rem;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
background-color: var(--bs-body-bg);
|
||||||
|
border: 1px solid var(--bs-border-color);
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.feature-card:hover {
|
||||||
|
transform: translateY(-5px);
|
||||||
|
box-shadow: 0 6px 12px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
@endpush
|
||||||
|
|
||||||
|
@section('content')
|
||||||
|
<div class="hero-section text-center">
|
||||||
|
<h1 class="display-4 fw-bold mb-3">JSON Lint Tool</h1>
|
||||||
|
<p class="lead mb-4">Validate, format, and beautify your JSON data with our powerful online tool</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row mb-4">
|
||||||
|
<div class="col-md-4 mb-3">
|
||||||
|
<div class="feature-card">
|
||||||
|
<i class="bi bi-check-circle feature-icon"></i>
|
||||||
|
<h3>Validate JSON</h3>
|
||||||
|
<p>Quickly check if your JSON data is valid and well-formed</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4 mb-3">
|
||||||
|
<div class="feature-card">
|
||||||
|
<i class="bi bi-code-slash feature-icon"></i>
|
||||||
|
<h3>Format JSON</h3>
|
||||||
|
<p>Automatically format and beautify your JSON for better readability</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4 mb-3">
|
||||||
|
<div class="feature-card">
|
||||||
|
<i class="bi bi-diagram-3 feature-icon"></i>
|
||||||
|
<h3>Tree View</h3>
|
||||||
|
<p>Visualize your JSON structure with an interactive tree view</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card shadow-sm">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||||
|
<h2 class="h4 mb-0">JSON Editor</h2>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="jsonInput" class="form-label">Enter your JSON:</label>
|
||||||
|
<textarea class="form-control json-input" id="jsonInput" rows="10" placeholder='{
|
||||||
|
"example": "json",
|
||||||
|
"format": "here"
|
||||||
|
}'></textarea>
|
||||||
|
</div>
|
||||||
|
<div class="d-flex gap-2">
|
||||||
|
<button class="btn btn-primary" id="validateBtn">
|
||||||
|
<i class="bi bi-check-circle me-2"></i>Validate JSON
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-outline-secondary" id="formatBtn">
|
||||||
|
<i class="bi bi-code-slash me-2"></i>Format JSON
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-outline-secondary" id="shareBtn">
|
||||||
|
<i class="bi bi-share me-2"></i>Share
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-outline-secondary" id="clearBtn">
|
||||||
|
<i class="bi bi-trash me-2"></i>Clear
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- New section for formatted JSON with tabs -->
|
||||||
|
<div class="card shadow-sm mt-4" id="formattedJsonBox">
|
||||||
|
<div class="card-body">
|
||||||
|
<h3 class="h5 card-title mb-3">Formatted JSON</h3>
|
||||||
|
|
||||||
|
<ul class="nav nav-tabs" id="jsonTabs" role="tablist">
|
||||||
|
<li class="nav-item" role="presentation">
|
||||||
|
<button class="nav-link active" id="tree-tab" data-bs-toggle="tab" data-bs-target="#tree-view" type="button" role="tab">
|
||||||
|
<i class="bi bi-diagram-3 me-2"></i>Tree View
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item" role="presentation">
|
||||||
|
<button class="nav-link" id="raw-tab" data-bs-toggle="tab" data-bs-target="#raw-view" type="button" role="tab">
|
||||||
|
<i class="bi bi-code me-2"></i>Raw JSON
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<div class="tab-content mt-3" id="jsonTabsContent">
|
||||||
|
<div class="tab-pane fade show active" id="tree-view" role="tabpanel">
|
||||||
|
<div class="tree-view-container" id="treeView"></div>
|
||||||
|
</div>
|
||||||
|
<div class="tab-pane fade" id="raw-view" role="tabpanel">
|
||||||
|
<pre class="tree-view-container" id="rawView"></pre>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-5">
|
||||||
|
<h2 class="h4 mb-3">About JSON Lint Tool</h2>
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-body">
|
||||||
|
<p>Our JSON Lint Tool is a free online service that helps developers and data analysts work with JSON data more efficiently. Whether you're validating API responses, debugging JSON configurations, or just need to format your JSON data, our tool provides a simple and intuitive interface to get the job done.</p>
|
||||||
|
|
||||||
|
<h3 class="h5 mt-4">Key Features:</h3>
|
||||||
|
<ul>
|
||||||
|
<li>Real-time JSON validation</li>
|
||||||
|
<li>Automatic JSON formatting and beautification</li>
|
||||||
|
<li>Interactive tree view for easy navigation</li>
|
||||||
|
<li>Dark and light mode support</li>
|
||||||
|
<li>Mobile-friendly interface</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h3 class="h5 mt-4">What is JSON?</h3>
|
||||||
|
<p>JSON (JavaScript Object Notation) is a lightweight data-interchange format that is easy for humans to read and write and easy for machines to parse and generate. It is widely used for transmitting data between a server and web application, as well as for configuration files and data storage.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endsection
|
||||||
|
|
||||||
|
@push('scripts')
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/jsoneditor@9.10.2/dist/jsoneditor.min.js"></script>
|
||||||
|
<script>
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
const jsonInput = document.getElementById('jsonInput');
|
||||||
|
const validateBtn = document.getElementById('validateBtn');
|
||||||
|
const formatBtn = document.getElementById('formatBtn');
|
||||||
|
const shareBtn = document.getElementById('shareBtn');
|
||||||
|
const clearBtn = document.getElementById('clearBtn');
|
||||||
|
const treeView = document.getElementById('treeView');
|
||||||
|
const rawView = document.getElementById('rawView');
|
||||||
|
const formattedJsonBox = document.getElementById('formattedJsonBox');
|
||||||
|
|
||||||
|
let editor = null;
|
||||||
|
|
||||||
|
function showError(message) {
|
||||||
|
const errorAlert = document.createElement('div');
|
||||||
|
errorAlert.className = 'alert alert-danger alert-dismissible fade show';
|
||||||
|
errorAlert.innerHTML = `
|
||||||
|
<i class="bi bi-exclamation-circle-fill me-2"></i>
|
||||||
|
${message}
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||||
|
`;
|
||||||
|
formattedJsonBox.querySelector('.card-body').insertBefore(errorAlert, formattedJsonBox.querySelector('.nav-tabs'));
|
||||||
|
}
|
||||||
|
|
||||||
|
function showSuccess(message) {
|
||||||
|
const successAlert = document.createElement('div');
|
||||||
|
successAlert.className = 'alert alert-success alert-dismissible fade show';
|
||||||
|
successAlert.innerHTML = `
|
||||||
|
<i class="bi bi-check-circle-fill me-2"></i>
|
||||||
|
${message}
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||||
|
`;
|
||||||
|
formattedJsonBox.querySelector('.card-body').insertBefore(successAlert, formattedJsonBox.querySelector('.nav-tabs'));
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateTreeView(json) {
|
||||||
|
// Clear previous editor
|
||||||
|
if (editor) {
|
||||||
|
editor.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create new editor with enhanced options
|
||||||
|
const options = {
|
||||||
|
mode: 'tree',
|
||||||
|
modes: ['tree', 'view', 'form', 'code', 'text'],
|
||||||
|
theme: document.documentElement.getAttribute('data-bs-theme') === 'dark' ? 'ace/theme/monokai' : 'ace/theme/github',
|
||||||
|
enableSort: false,
|
||||||
|
enableTransform: false,
|
||||||
|
search: true,
|
||||||
|
history: true,
|
||||||
|
navigationBar: true,
|
||||||
|
statusBar: true,
|
||||||
|
mainMenuBar: true,
|
||||||
|
onChange: function() {
|
||||||
|
try {
|
||||||
|
const updatedJson = editor.get();
|
||||||
|
jsonInput.value = JSON.stringify(updatedJson, null, 2);
|
||||||
|
rawView.textContent = JSON.stringify(updatedJson, null, 2);
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Error updating JSON:', e);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onNodeName: function(node) {
|
||||||
|
// Safely handle node animation
|
||||||
|
if (node && node.dom && node.dom.expandable) {
|
||||||
|
const expandable = node.dom.expandable;
|
||||||
|
expandable.classList.add('expanded');
|
||||||
|
setTimeout(() => {
|
||||||
|
expandable.classList.remove('expanded');
|
||||||
|
}, 300);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onEditable: function(node) {
|
||||||
|
return false; // Make the tree view read-only
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
editor = new JSONEditor(treeView, options);
|
||||||
|
|
||||||
|
// Add initial animation
|
||||||
|
setTimeout(() => {
|
||||||
|
const container = treeView.querySelector('.jsoneditor-tree');
|
||||||
|
if (container) {
|
||||||
|
container.style.opacity = '0';
|
||||||
|
container.style.transform = 'translateY(20px)';
|
||||||
|
setTimeout(() => {
|
||||||
|
container.style.opacity = '1';
|
||||||
|
container.style.transform = 'translateY(0)';
|
||||||
|
}, 100);
|
||||||
|
}
|
||||||
|
}, 100);
|
||||||
|
|
||||||
|
// Set the JSON after a small delay to ensure DOM is ready
|
||||||
|
setTimeout(() => {
|
||||||
|
editor.set(json);
|
||||||
|
}, 150);
|
||||||
|
|
||||||
|
// Update raw view
|
||||||
|
rawView.textContent = JSON.stringify(json, null, 2);
|
||||||
|
|
||||||
|
// Show the formatted JSON box
|
||||||
|
document.getElementById('formattedJsonBox').style.display = 'block';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hide formatted JSON box initially
|
||||||
|
document.getElementById('formattedJsonBox').style.display = 'none';
|
||||||
|
|
||||||
|
// Load saved JSON from local storage
|
||||||
|
const savedJson = localStorage.getItem('jsonlint_history');
|
||||||
|
if (savedJson) {
|
||||||
|
try {
|
||||||
|
const parsedJson = JSON.parse(savedJson);
|
||||||
|
jsonInput.value = JSON.stringify(parsedJson, null, 2);
|
||||||
|
updateTreeView(parsedJson);
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Error loading saved JSON:', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save JSON to local storage
|
||||||
|
function saveToHistory() {
|
||||||
|
try {
|
||||||
|
const json = JSON.parse(jsonInput.value);
|
||||||
|
if (json && Object.keys(json).length > 0) {
|
||||||
|
localStorage.setItem('jsonlint_history', JSON.stringify(json));
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Error saving JSON to history:', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate shareable link
|
||||||
|
function generateShareLink() {
|
||||||
|
try {
|
||||||
|
const json = JSON.parse(jsonInput.value);
|
||||||
|
const jsonString = JSON.stringify(json);
|
||||||
|
const encodedJson = encodeURIComponent(jsonString);
|
||||||
|
const url = new URL(window.location.href);
|
||||||
|
url.searchParams.set('json', encodedJson);
|
||||||
|
return url.toString();
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Error generating share link:', e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Share button click handler
|
||||||
|
shareBtn.addEventListener('click', function() {
|
||||||
|
const shareLink = generateShareLink();
|
||||||
|
if (shareLink) {
|
||||||
|
// Copy to clipboard
|
||||||
|
navigator.clipboard.writeText(shareLink).then(function() {
|
||||||
|
// Show success message
|
||||||
|
const originalText = shareBtn.innerHTML;
|
||||||
|
shareBtn.innerHTML = '<i class="bi bi-check-circle me-2"></i>Copied!';
|
||||||
|
setTimeout(function() {
|
||||||
|
shareBtn.innerHTML = originalText;
|
||||||
|
}, 2000);
|
||||||
|
}).catch(function(err) {
|
||||||
|
console.error('Error copying to clipboard:', err);
|
||||||
|
alert('Failed to copy link to clipboard. Please copy the URL manually.');
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
alert('Failed to generate share link. Please make sure your JSON is valid.');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Validate JSON
|
||||||
|
validateBtn.addEventListener('click', function() {
|
||||||
|
try {
|
||||||
|
const json = JSON.parse(jsonInput.value);
|
||||||
|
showSuccess('Valid JSON!');
|
||||||
|
updateTreeView(json);
|
||||||
|
} catch (e) {
|
||||||
|
showError(`Invalid JSON: ${e.message}`);
|
||||||
|
formattedJsonBox.style.display = 'block';
|
||||||
|
if (editor) {
|
||||||
|
editor.destroy();
|
||||||
|
editor = null;
|
||||||
|
}
|
||||||
|
treeView.innerHTML = '';
|
||||||
|
rawView.textContent = '';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Format JSON
|
||||||
|
formatBtn.addEventListener('click', function() {
|
||||||
|
try {
|
||||||
|
const json = JSON.parse(jsonInput.value);
|
||||||
|
jsonInput.value = JSON.stringify(json, null, 2);
|
||||||
|
showSuccess('JSON formatted successfully!');
|
||||||
|
updateTreeView(json);
|
||||||
|
} catch (e) {
|
||||||
|
showError(`Cannot format invalid JSON: ${e.message}`);
|
||||||
|
formattedJsonBox.style.display = 'block';
|
||||||
|
if (editor) {
|
||||||
|
editor.destroy();
|
||||||
|
editor = null;
|
||||||
|
}
|
||||||
|
treeView.innerHTML = '';
|
||||||
|
rawView.textContent = '';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Clear input
|
||||||
|
clearBtn.addEventListener('click', function() {
|
||||||
|
jsonInput.value = '';
|
||||||
|
formattedJsonBox.style.display = 'none';
|
||||||
|
if (editor) {
|
||||||
|
editor.destroy();
|
||||||
|
editor = null;
|
||||||
|
}
|
||||||
|
treeView.innerHTML = '';
|
||||||
|
rawView.textContent = '';
|
||||||
|
localStorage.removeItem('jsonlint_history');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Auto-save when input changes
|
||||||
|
jsonInput.addEventListener('input', function() {
|
||||||
|
try {
|
||||||
|
const json = JSON.parse(jsonInput.value);
|
||||||
|
if (json && Object.keys(json).length > 0) {
|
||||||
|
saveToHistory();
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// Don't save invalid JSON
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Load JSON from URL if present
|
||||||
|
const urlParams = new URLSearchParams(window.location.search);
|
||||||
|
const jsonParam = urlParams.get('json');
|
||||||
|
if (jsonParam) {
|
||||||
|
try {
|
||||||
|
const decodedJson = decodeURIComponent(jsonParam);
|
||||||
|
const json = JSON.parse(decodedJson);
|
||||||
|
jsonInput.value = JSON.stringify(json, null, 2);
|
||||||
|
updateTreeView(json);
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Error loading JSON from URL:', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
@endpush
|
||||||
@@ -0,0 +1,356 @@
|
|||||||
|
@extends('layouts.app')
|
||||||
|
|
||||||
|
@section('title', 'JSON Schema Validator - Free Online JSON Schema Validation Tool')
|
||||||
|
|
||||||
|
@push('styles')
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/jsoneditor@9.10.2/dist/jsoneditor.min.css" rel="stylesheet">
|
||||||
|
<style>
|
||||||
|
.json-input {
|
||||||
|
min-height: 300px;
|
||||||
|
font-family: 'Courier New', monospace;
|
||||||
|
background-color: var(--bs-body-bg);
|
||||||
|
border: 1px solid var(--bs-border-color);
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.json-input:focus {
|
||||||
|
border-color: var(--primary-color);
|
||||||
|
box-shadow: 0 0 0 0.25rem rgba(74, 107, 255, 0.25);
|
||||||
|
}
|
||||||
|
|
||||||
|
.result-box {
|
||||||
|
display: none;
|
||||||
|
animation: fadeIn 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fadeIn {
|
||||||
|
from { opacity: 0; transform: translateY(-10px); }
|
||||||
|
to { opacity: 1; transform: translateY(0); }
|
||||||
|
}
|
||||||
|
|
||||||
|
.result-box.show {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tree-view-container {
|
||||||
|
height: 500px;
|
||||||
|
border: 1px solid var(--bs-border-color);
|
||||||
|
border-radius: 0.375rem;
|
||||||
|
margin-top: 1rem;
|
||||||
|
background-color: var(--bs-body-bg);
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-tabs {
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
border-bottom: 1px solid var(--bs-border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-tabs .nav-link {
|
||||||
|
color: var(--bs-body-color);
|
||||||
|
border: none;
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-tabs .nav-link.active {
|
||||||
|
color: var(--primary-color);
|
||||||
|
border-bottom: 2px solid var(--primary-color);
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-tabs .nav-link:hover {
|
||||||
|
color: var(--primary-color);
|
||||||
|
border-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.jsoneditor {
|
||||||
|
border: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.jsoneditor-menu {
|
||||||
|
background-color: var(--bs-body-bg) !important;
|
||||||
|
border-bottom: 1px solid var(--bs-border-color) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.jsoneditor-navigation-bar {
|
||||||
|
background-color: var(--bs-body-bg) !important;
|
||||||
|
border-bottom: 1px solid var(--bs-border-color) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-section {
|
||||||
|
background: linear-gradient(135deg, var(--primary-color) 0%, #6a8eff 100%);
|
||||||
|
color: white;
|
||||||
|
padding: 3rem 0;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.feature-icon {
|
||||||
|
font-size: 2rem;
|
||||||
|
color: var(--primary-color);
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.feature-card {
|
||||||
|
text-align: center;
|
||||||
|
padding: 1.5rem;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
background-color: var(--bs-body-bg);
|
||||||
|
border: 1px solid var(--bs-border-color);
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.feature-card:hover {
|
||||||
|
transform: translateY(-5px);
|
||||||
|
box-shadow: 0 6px 12px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
@endpush
|
||||||
|
|
||||||
|
@section('content')
|
||||||
|
<div class="hero-section text-center">
|
||||||
|
<h1 class="display-4 fw-bold mb-3">JSON Schema Validator</h1>
|
||||||
|
<p class="lead mb-4">Validate your JSON data against a JSON Schema</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row mb-4">
|
||||||
|
<div class="col-md-4 mb-3">
|
||||||
|
<div class="feature-card">
|
||||||
|
<i class="bi bi-file-earmark-check feature-icon"></i>
|
||||||
|
<h3>Schema Validation</h3>
|
||||||
|
<p>Validate your JSON data against a JSON Schema specification</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4 mb-3">
|
||||||
|
<div class="feature-card">
|
||||||
|
<i class="bi bi-code-slash feature-icon"></i>
|
||||||
|
<h3>Interactive Editor</h3>
|
||||||
|
<p>Edit both JSON and Schema in a user-friendly interface</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4 mb-3">
|
||||||
|
<div class="feature-card">
|
||||||
|
<i class="bi bi-lightning-charge feature-icon"></i>
|
||||||
|
<h3>Real-time Feedback</h3>
|
||||||
|
<p>Get instant validation results with detailed error messages</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card shadow-sm">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6 mb-3">
|
||||||
|
<label for="jsonInput" class="form-label">JSON Data:</label>
|
||||||
|
<textarea class="form-control json-input" id="jsonInput" rows="10" placeholder='{
|
||||||
|
"name": "John Doe",
|
||||||
|
"age": 30,
|
||||||
|
"email": "john@example.com"
|
||||||
|
}'></textarea>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6 mb-3">
|
||||||
|
<label for="schemaInput" class="form-label">JSON Schema:</label>
|
||||||
|
<textarea class="form-control json-input" id="schemaInput" rows="10" placeholder='{
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"name": { "type": "string" },
|
||||||
|
"age": { "type": "number" },
|
||||||
|
"email": { "type": "string", "format": "email" }
|
||||||
|
},
|
||||||
|
"required": ["name", "age", "email"]
|
||||||
|
}'></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="d-flex gap-2">
|
||||||
|
<button class="btn btn-primary" id="validateBtn">
|
||||||
|
<i class="bi bi-check-circle me-2"></i>Validate
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-outline-secondary" id="clearBtn">
|
||||||
|
<i class="bi bi-trash me-2"></i>Clear
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card shadow-sm mt-4 result-box" id="resultBox">
|
||||||
|
<div class="card-body">
|
||||||
|
<h3 class="h5 card-title">Validation Result</h3>
|
||||||
|
<div id="resultContent"></div>
|
||||||
|
|
||||||
|
<ul class="nav nav-tabs" id="viewTabs" role="tablist">
|
||||||
|
<li class="nav-item" role="presentation">
|
||||||
|
<button class="nav-link active" id="tree-tab" data-bs-toggle="tab" data-bs-target="#tree-view" type="button" role="tab">Tree View</button>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item" role="presentation">
|
||||||
|
<button class="nav-link" id="raw-tab" data-bs-toggle="tab" data-bs-target="#raw-view" type="button" role="tab">Raw JSON</button>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<div class="tab-content" id="viewTabsContent">
|
||||||
|
<div class="tab-pane fade show active" id="tree-view" role="tabpanel">
|
||||||
|
<div class="tree-view-container" id="treeView"></div>
|
||||||
|
</div>
|
||||||
|
<div class="tab-pane fade" id="raw-view" role="tabpanel">
|
||||||
|
<pre class="tree-view-container" id="rawView"></pre>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-5">
|
||||||
|
<h2 class="h4 mb-3">About JSON Schema Validation</h2>
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-body">
|
||||||
|
<p>JSON Schema is a powerful tool for validating the structure of JSON data. It allows you to define the expected format, data types, and constraints for your JSON documents. Our JSON Schema Validator makes it easy to validate your JSON data against a schema specification.</p>
|
||||||
|
|
||||||
|
<h3 class="h5 mt-4">Key Features:</h3>
|
||||||
|
<ul>
|
||||||
|
<li>Validate JSON data against JSON Schema specifications</li>
|
||||||
|
<li>Interactive JSON and Schema editors</li>
|
||||||
|
<li>Detailed validation error messages</li>
|
||||||
|
<li>Tree view and raw JSON view options</li>
|
||||||
|
<li>Dark and light mode support</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h3 class="h5 mt-4">How to Use:</h3>
|
||||||
|
<ol>
|
||||||
|
<li>Enter or paste your JSON data in the left textarea</li>
|
||||||
|
<li>Enter or paste your JSON Schema in the right textarea</li>
|
||||||
|
<li>Click the "Validate" button to check if your JSON conforms to the schema</li>
|
||||||
|
<li>View the validation results and any error messages</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<h3 class="h5 mt-4">Example Schema:</h3>
|
||||||
|
<pre class="bg-light p-3 rounded"><code>{
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"name": { "type": "string" },
|
||||||
|
"age": { "type": "number", "minimum": 0 },
|
||||||
|
"email": { "type": "string", "format": "email" }
|
||||||
|
},
|
||||||
|
"required": ["name", "age", "email"]
|
||||||
|
}</code></pre>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endsection
|
||||||
|
|
||||||
|
@push('scripts')
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/jsoneditor@9.10.2/dist/jsoneditor.min.js"></script>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/ajv@8.12.0/dist/ajv.min.js"></script>
|
||||||
|
<script>
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
const validateBtn = document.getElementById('validateBtn');
|
||||||
|
const clearBtn = document.getElementById('clearBtn');
|
||||||
|
const jsonInput = document.getElementById('jsonInput');
|
||||||
|
const schemaInput = document.getElementById('schemaInput');
|
||||||
|
const resultBox = document.getElementById('resultBox');
|
||||||
|
const resultContent = document.getElementById('resultContent');
|
||||||
|
const treeView = document.getElementById('treeView');
|
||||||
|
const rawView = document.getElementById('rawView');
|
||||||
|
|
||||||
|
let editor = null;
|
||||||
|
const ajv = new Ajv();
|
||||||
|
|
||||||
|
function updateTreeView(json) {
|
||||||
|
// Clear previous editor
|
||||||
|
if (editor) {
|
||||||
|
editor.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create new editor
|
||||||
|
const options = {
|
||||||
|
mode: 'tree',
|
||||||
|
modes: ['tree', 'view', 'form', 'code', 'text'],
|
||||||
|
theme: document.documentElement.getAttribute('data-bs-theme') === 'dark' ? 'ace/theme/monokai' : 'ace/theme/github',
|
||||||
|
onChange: function() {
|
||||||
|
try {
|
||||||
|
const updatedJson = editor.get();
|
||||||
|
jsonInput.value = JSON.stringify(updatedJson, null, 2);
|
||||||
|
rawView.textContent = JSON.stringify(updatedJson, null, 2);
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Error updating JSON:', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
editor = new JSONEditor(treeView, options);
|
||||||
|
editor.set(json);
|
||||||
|
|
||||||
|
// Update raw view
|
||||||
|
rawView.textContent = JSON.stringify(json, null, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate JSON against Schema
|
||||||
|
validateBtn.addEventListener('click', function() {
|
||||||
|
try {
|
||||||
|
const json = JSON.parse(jsonInput.value);
|
||||||
|
const schema = JSON.parse(schemaInput.value);
|
||||||
|
|
||||||
|
// Compile schema
|
||||||
|
const validate = ajv.compile(schema);
|
||||||
|
const valid = validate(json);
|
||||||
|
|
||||||
|
if (valid) {
|
||||||
|
resultContent.innerHTML = `
|
||||||
|
<div class="alert alert-success">
|
||||||
|
<i class="bi bi-check-circle-fill me-2"></i>
|
||||||
|
JSON data is valid according to the schema!
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
resultBox.classList.add('show');
|
||||||
|
updateTreeView(json);
|
||||||
|
} else {
|
||||||
|
const errors = validate.errors.map(error => {
|
||||||
|
return `<li>${error.instancePath || 'root'}: ${error.message}</li>`;
|
||||||
|
}).join('');
|
||||||
|
|
||||||
|
resultContent.innerHTML = `
|
||||||
|
<div class="alert alert-danger">
|
||||||
|
<i class="bi bi-exclamation-circle-fill me-2"></i>
|
||||||
|
JSON data is invalid according to the schema:
|
||||||
|
<ul class="mt-2 mb-0">${errors}</ul>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
resultBox.classList.add('show');
|
||||||
|
if (editor) {
|
||||||
|
editor.destroy();
|
||||||
|
editor = null;
|
||||||
|
}
|
||||||
|
treeView.innerHTML = '';
|
||||||
|
rawView.textContent = '';
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
resultContent.innerHTML = `
|
||||||
|
<div class="alert alert-danger">
|
||||||
|
<i class="bi bi-exclamation-circle-fill me-2"></i>
|
||||||
|
Error: ${e.message}
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
resultBox.classList.add('show');
|
||||||
|
if (editor) {
|
||||||
|
editor.destroy();
|
||||||
|
editor = null;
|
||||||
|
}
|
||||||
|
treeView.innerHTML = '';
|
||||||
|
rawView.textContent = '';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Clear input
|
||||||
|
clearBtn.addEventListener('click', function() {
|
||||||
|
jsonInput.value = '';
|
||||||
|
schemaInput.value = '';
|
||||||
|
resultBox.classList.remove('show');
|
||||||
|
if (editor) {
|
||||||
|
editor.destroy();
|
||||||
|
editor = null;
|
||||||
|
}
|
||||||
|
treeView.innerHTML = '';
|
||||||
|
rawView.textContent = '';
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
@endpush
|
||||||
@@ -0,0 +1,384 @@
|
|||||||
|
@extends('layouts.app')
|
||||||
|
|
||||||
|
@section('title', 'JSON Templates - Common JSON Formats and Examples')
|
||||||
|
|
||||||
|
@push('styles')
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/jsoneditor@9.10.2/dist/jsoneditor.min.css" rel="stylesheet">
|
||||||
|
<style>
|
||||||
|
.template-card {
|
||||||
|
border: 1px solid var(--bs-border-color);
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.template-card:hover {
|
||||||
|
transform: translateY(-5px);
|
||||||
|
box-shadow: 0 6px 12px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.template-card .card-body {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.template-card .card-footer {
|
||||||
|
background: transparent;
|
||||||
|
border-top: 1px solid var(--bs-border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.template-preview {
|
||||||
|
background-color: var(--bs-body-bg);
|
||||||
|
border: 1px solid var(--bs-border-color);
|
||||||
|
border-radius: 0.375rem;
|
||||||
|
padding: 1rem;
|
||||||
|
margin-top: 1rem;
|
||||||
|
max-height: 300px;
|
||||||
|
overflow-y: auto;
|
||||||
|
font-family: 'Courier New', monospace;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.template-icon {
|
||||||
|
font-size: 2rem;
|
||||||
|
color: var(--primary-color);
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.template-category {
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.template-category h2 {
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
padding-bottom: 0.5rem;
|
||||||
|
border-bottom: 2px solid var(--primary-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.copy-btn {
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.copy-btn:hover {
|
||||||
|
color: var(--primary-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.copy-btn.copied {
|
||||||
|
color: var(--bs-success);
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-section {
|
||||||
|
background: linear-gradient(135deg, var(--primary-color) 0%, #6a8eff 100%);
|
||||||
|
color: white;
|
||||||
|
padding: 3rem 0;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
@endpush
|
||||||
|
|
||||||
|
@section('content')
|
||||||
|
<div class="hero-section text-center">
|
||||||
|
<h1 class="display-4 fw-bold mb-3">JSON Templates</h1>
|
||||||
|
<p class="lead mb-4">Common JSON formats and examples for various use cases</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="template-category">
|
||||||
|
<h2 class="h4">API Responses</h2>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6 mb-4">
|
||||||
|
<div class="card template-card">
|
||||||
|
<div class="card-body">
|
||||||
|
<i class="bi bi-cloud-arrow-down template-icon"></i>
|
||||||
|
<h3 class="h5">REST API Response</h3>
|
||||||
|
<p class="text-muted">Standard REST API response with status, data, and pagination</p>
|
||||||
|
<div class="template-preview">
|
||||||
|
<pre><code>{
|
||||||
|
"status": "success",
|
||||||
|
"code": 200,
|
||||||
|
"message": "Data retrieved successfully",
|
||||||
|
"data": {
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"name": "Item 1",
|
||||||
|
"description": "Description of item 1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 2,
|
||||||
|
"name": "Item 2",
|
||||||
|
"description": "Description of item 2"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"pagination": {
|
||||||
|
"total": 100,
|
||||||
|
"per_page": 10,
|
||||||
|
"current_page": 1,
|
||||||
|
"last_page": 10
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}</code></pre>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card-footer">
|
||||||
|
<button class="btn btn-link copy-btn" data-template="rest-api">
|
||||||
|
<i class="bi bi-clipboard me-2"></i>Copy Template
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6 mb-4">
|
||||||
|
<div class="card template-card">
|
||||||
|
<div class="card-body">
|
||||||
|
<i class="bi bi-graph-up template-icon"></i>
|
||||||
|
<h3 class="h5">GraphQL Response</h3>
|
||||||
|
<p class="text-muted">GraphQL API response with nested data structure</p>
|
||||||
|
<div class="template-preview">
|
||||||
|
<pre><code>{
|
||||||
|
"data": {
|
||||||
|
"user": {
|
||||||
|
"id": "1",
|
||||||
|
"name": "John Doe",
|
||||||
|
"email": "john@example.com",
|
||||||
|
"posts": [
|
||||||
|
{
|
||||||
|
"id": "101",
|
||||||
|
"title": "First Post",
|
||||||
|
"content": "Content of the first post",
|
||||||
|
"comments": [
|
||||||
|
{
|
||||||
|
"id": "1001",
|
||||||
|
"text": "Great post!",
|
||||||
|
"author": {
|
||||||
|
"id": "2",
|
||||||
|
"name": "Jane Smith"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}</code></pre>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card-footer">
|
||||||
|
<button class="btn btn-link copy-btn" data-template="graphql">
|
||||||
|
<i class="bi bi-clipboard me-2"></i>Copy Template
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="template-category">
|
||||||
|
<h2 class="h4">Configuration Files</h2>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6 mb-4">
|
||||||
|
<div class="card template-card">
|
||||||
|
<div class="card-body">
|
||||||
|
<i class="bi bi-gear template-icon"></i>
|
||||||
|
<h3 class="h5">Application Config</h3>
|
||||||
|
<p class="text-muted">Common application configuration settings</p>
|
||||||
|
<div class="template-preview">
|
||||||
|
<pre><code>{
|
||||||
|
"app": {
|
||||||
|
"name": "My Application",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"environment": "production",
|
||||||
|
"debug": false
|
||||||
|
},
|
||||||
|
"database": {
|
||||||
|
"host": "localhost",
|
||||||
|
"port": 3306,
|
||||||
|
"name": "mydb",
|
||||||
|
"username": "user",
|
||||||
|
"password": "secret"
|
||||||
|
},
|
||||||
|
"logging": {
|
||||||
|
"level": "info",
|
||||||
|
"file": "app.log",
|
||||||
|
"max_size": "10MB"
|
||||||
|
},
|
||||||
|
"api": {
|
||||||
|
"base_url": "https://api.example.com",
|
||||||
|
"timeout": 30,
|
||||||
|
"retry_attempts": 3
|
||||||
|
}
|
||||||
|
}</code></pre>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card-footer">
|
||||||
|
<button class="btn btn-link copy-btn" data-template="app-config">
|
||||||
|
<i class="bi bi-clipboard me-2"></i>Copy Template
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6 mb-4">
|
||||||
|
<div class="card template-card">
|
||||||
|
<div class="card-body">
|
||||||
|
<i class="bi bi-box template-icon"></i>
|
||||||
|
<h3 class="h5">Package.json</h3>
|
||||||
|
<p class="text-muted">Node.js package configuration</p>
|
||||||
|
<div class="template-preview">
|
||||||
|
<pre><code>{
|
||||||
|
"name": "my-package",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "A sample Node.js package",
|
||||||
|
"main": "index.js",
|
||||||
|
"scripts": {
|
||||||
|
"start": "node index.js",
|
||||||
|
"test": "jest",
|
||||||
|
"build": "webpack"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"express": "^4.17.1",
|
||||||
|
"lodash": "^4.17.21"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"jest": "^27.0.6",
|
||||||
|
"webpack": "^5.50.0"
|
||||||
|
},
|
||||||
|
"author": "John Doe",
|
||||||
|
"license": "MIT"
|
||||||
|
}</code></pre>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card-footer">
|
||||||
|
<button class="btn btn-link copy-btn" data-template="package-json">
|
||||||
|
<i class="bi bi-clipboard me-2"></i>Copy Template
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="template-category">
|
||||||
|
<h2 class="h4">Data Structures</h2>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6 mb-4">
|
||||||
|
<div class="card template-card">
|
||||||
|
<div class="card-body">
|
||||||
|
<i class="bi bi-person template-icon"></i>
|
||||||
|
<h3 class="h5">User Profile</h3>
|
||||||
|
<p class="text-muted">User profile data structure</p>
|
||||||
|
<div class="template-preview">
|
||||||
|
<pre><code>{
|
||||||
|
"id": "user123",
|
||||||
|
"username": "johndoe",
|
||||||
|
"email": "john@example.com",
|
||||||
|
"profile": {
|
||||||
|
"firstName": "John",
|
||||||
|
"lastName": "Doe",
|
||||||
|
"age": 30,
|
||||||
|
"gender": "male",
|
||||||
|
"location": {
|
||||||
|
"country": "USA",
|
||||||
|
"city": "New York"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"preferences": {
|
||||||
|
"theme": "dark",
|
||||||
|
"notifications": true,
|
||||||
|
"language": "en"
|
||||||
|
},
|
||||||
|
"createdAt": "2023-01-01T00:00:00Z",
|
||||||
|
"lastLogin": "2023-06-15T14:30:00Z"
|
||||||
|
}</code></pre>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card-footer">
|
||||||
|
<button class="btn btn-link copy-btn" data-template="user-profile">
|
||||||
|
<i class="bi bi-clipboard me-2"></i>Copy Template
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6 mb-4">
|
||||||
|
<div class="card template-card">
|
||||||
|
<div class="card-body">
|
||||||
|
<i class="bi bi-cart template-icon"></i>
|
||||||
|
<h3 class="h5">E-commerce Order</h3>
|
||||||
|
<p class="text-muted">E-commerce order data structure</p>
|
||||||
|
<div class="template-preview">
|
||||||
|
<pre><code>{
|
||||||
|
"orderId": "ORD-2023-001",
|
||||||
|
"customer": {
|
||||||
|
"id": "CUST-001",
|
||||||
|
"name": "John Doe",
|
||||||
|
"email": "john@example.com",
|
||||||
|
"shippingAddress": {
|
||||||
|
"street": "123 Main St",
|
||||||
|
"city": "New York",
|
||||||
|
"state": "NY",
|
||||||
|
"zipCode": "10001",
|
||||||
|
"country": "USA"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"productId": "PROD-001",
|
||||||
|
"name": "Product 1",
|
||||||
|
"quantity": 2,
|
||||||
|
"price": 29.99,
|
||||||
|
"total": 59.98
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"productId": "PROD-002",
|
||||||
|
"name": "Product 2",
|
||||||
|
"quantity": 1,
|
||||||
|
"price": 49.99,
|
||||||
|
"total": 49.99
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"subtotal": 109.97,
|
||||||
|
"tax": 10.99,
|
||||||
|
"shipping": 5.99,
|
||||||
|
"total": 126.95,
|
||||||
|
"status": "processing",
|
||||||
|
"createdAt": "2023-06-15T10:30:00Z"
|
||||||
|
}</code></pre>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card-footer">
|
||||||
|
<button class="btn btn-link copy-btn" data-template="ecommerce-order">
|
||||||
|
<i class="bi bi-clipboard me-2"></i>Copy Template
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endsection
|
||||||
|
|
||||||
|
@push('scripts')
|
||||||
|
<script>
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
const copyButtons = document.querySelectorAll('.copy-btn');
|
||||||
|
|
||||||
|
copyButtons.forEach(button => {
|
||||||
|
button.addEventListener('click', function() {
|
||||||
|
const template = this.getAttribute('data-template');
|
||||||
|
const templateContent = this.closest('.card').querySelector('pre code').textContent;
|
||||||
|
|
||||||
|
navigator.clipboard.writeText(templateContent).then(() => {
|
||||||
|
const originalText = this.innerHTML;
|
||||||
|
this.innerHTML = '<i class="bi bi-check2 me-2"></i>Copied!';
|
||||||
|
this.classList.add('copied');
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
this.innerHTML = originalText;
|
||||||
|
this.classList.remove('copied');
|
||||||
|
}, 2000);
|
||||||
|
}).catch(err => {
|
||||||
|
console.error('Failed to copy text: ', err);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
@endpush
|
||||||
@@ -1,36 +1,145 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
|
<html lang="en" data-bs-theme="light">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<meta name="csrf-token" content="{{ csrf_token() }}">
|
<meta name="description" content="Free online JSON tools. Validate, format, and compare JSON documents with our easy-to-use tools. Perfect for developers and data analysts.">
|
||||||
|
<meta name="keywords" content="JSON validator, JSON formatter, JSON lint, JSON compare, JSON diff, JSON beautifier, JSON parser, JSON editor, JSON tool">
|
||||||
|
<meta name="author" content="JSON Tools">
|
||||||
|
<title>@yield('title', 'JSON Tools')</title>
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.0/font/bootstrap-icons.css" rel="stylesheet">
|
||||||
|
@stack('styles')
|
||||||
|
<style>
|
||||||
|
:root {
|
||||||
|
--primary-color: #4a6bff;
|
||||||
|
--primary-hover: #3a5bef;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
background-color: var(--bs-body-bg);
|
||||||
|
transition: background-color 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
<title>{{ config('app.name', 'Laravel') }}</title>
|
.navbar {
|
||||||
|
background-color: var(--bs-body-bg);
|
||||||
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||||
|
padding: 1rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
<!-- Fonts -->
|
.navbar-brand {
|
||||||
<link rel="preconnect" href="https://fonts.bunny.net">
|
font-weight: bold;
|
||||||
<link href="https://fonts.bunny.net/css?family=figtree:400,500,600&display=swap" rel="stylesheet" />
|
color: var(--primary-color) !important;
|
||||||
|
}
|
||||||
|
|
||||||
<!-- Scripts -->
|
.nav-link {
|
||||||
@vite(['resources/css/app.css', 'resources/js/app.js'])
|
color: var(--bs-body-color);
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
border-radius: 0.375rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-link:hover {
|
||||||
|
color: var(--primary-color);
|
||||||
|
background-color: rgba(74, 107, 255, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-link.active {
|
||||||
|
color: var(--primary-color);
|
||||||
|
background-color: rgba(74, 107, 255, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-switch {
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 0.5rem;
|
||||||
|
border-radius: 50%;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
background-color: var(--bs-body-bg);
|
||||||
|
border: 1px solid var(--bs-border-color);
|
||||||
|
margin-left: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-switch:hover {
|
||||||
|
background-color: var(--bs-border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
padding-top: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
@yield('styles')
|
||||||
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body class="font-sans antialiased">
|
<body>
|
||||||
<div class="min-h-screen bg-gray-100 dark:bg-gray-900">
|
<nav class="navbar navbar-expand-lg">
|
||||||
@include('layouts.navigation')
|
<div class="container">
|
||||||
|
<a class="navbar-brand" href="/">
|
||||||
|
<i class="bi bi-code-square me-2"></i>
|
||||||
|
JSON Tools
|
||||||
|
</a>
|
||||||
|
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
|
||||||
|
<span class="navbar-toggler-icon"></span>
|
||||||
|
</button>
|
||||||
|
<div class="collapse navbar-collapse" id="navbarNav">
|
||||||
|
<ul class="navbar-nav me-auto">
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link {{ request()->is('jsonlint') ? 'active' : '' }}" href="{{ url('/jsonlint') }}">
|
||||||
|
<i class="bi bi-code-slash me-2"></i>JSON Lint
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link {{ request()->is('jsoncompare') ? 'active' : '' }}" href="{{ url('/jsoncompare') }}">
|
||||||
|
<i class="bi bi-arrows-angle-contract me-2"></i>JSON Compare
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link {{ request()->is('jsonschema') ? 'active' : '' }}" href="{{ url('/jsonschema') }}">
|
||||||
|
<i class="bi bi-file-earmark-check me-2"></i>JSON Schema
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link {{ request()->is('jsontemplates') ? 'active' : '' }}" href="{{ url('/jsontemplates') }}">
|
||||||
|
<i class="bi bi-file-earmark-text me-2"></i>JSON Templates
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" href="{{ route('api') }}">API</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<button class="theme-switch" id="themeSwitch" title="Toggle dark/light mode">
|
||||||
|
<i class="bi bi-moon-fill"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
<!-- Page Heading -->
|
<div class="container">
|
||||||
@isset($header)
|
@yield('content')
|
||||||
<header class="bg-white dark:bg-gray-800 shadow">
|
|
||||||
<div class="max-w-7xl mx-auto py-6 px-4 sm:px-6 lg:px-8">
|
|
||||||
{{ $header }}
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
@endisset
|
|
||||||
|
|
||||||
<!-- Page Content -->
|
|
||||||
<main>
|
|
||||||
{{ $slot }}
|
|
||||||
</main>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||||
|
<script>
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
const themeSwitch = document.getElementById('themeSwitch');
|
||||||
|
const html = document.documentElement;
|
||||||
|
|
||||||
|
// Check for saved theme preference
|
||||||
|
const savedTheme = localStorage.getItem('theme');
|
||||||
|
if (savedTheme) {
|
||||||
|
html.setAttribute('data-bs-theme', savedTheme);
|
||||||
|
themeSwitch.innerHTML = savedTheme === 'dark' ? '<i class="bi bi-sun-fill"></i>' : '<i class="bi bi-moon-fill"></i>';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Theme switching
|
||||||
|
themeSwitch.addEventListener('click', function() {
|
||||||
|
const isDark = html.getAttribute('data-bs-theme') === 'dark';
|
||||||
|
const newTheme = isDark ? 'light' : 'dark';
|
||||||
|
html.setAttribute('data-bs-theme', newTheme);
|
||||||
|
themeSwitch.innerHTML = isDark ? '<i class="bi bi-moon-fill"></i>' : '<i class="bi bi-sun-fill"></i>';
|
||||||
|
localStorage.setItem('theme', newTheme);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
@stack('scripts')
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\Route;
|
||||||
|
use App\Http\Controllers\JsonApiController;
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| API Routes
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Here is where you can register API routes for your application. These
|
||||||
|
| routes are loaded by the RouteServiceProvider and all of them will
|
||||||
|
| be assigned to the "api" middleware group. Make something great!
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
Route::middleware('auth:sanctum')->get('/user', function (Request $request) {
|
||||||
|
return $request->user();
|
||||||
|
});
|
||||||
|
|
||||||
|
// JSON Validation API Routes
|
||||||
|
Route::prefix('json')->group(function () {
|
||||||
|
// Validate JSON
|
||||||
|
Route::post('/validate', [JsonApiController::class, 'validateJson']);
|
||||||
|
|
||||||
|
// Validate JSON against schema
|
||||||
|
Route::post('/validate-schema', [JsonApiController::class, 'validateSchema']);
|
||||||
|
|
||||||
|
// Format JSON
|
||||||
|
Route::post('/format', [JsonApiController::class, 'formatJson']);
|
||||||
|
|
||||||
|
// Compare JSON documents
|
||||||
|
Route::post('/compare', [JsonApiController::class, 'compareJson']);
|
||||||
|
});
|
||||||
+25
-1
@@ -4,13 +4,37 @@ use App\Http\Controllers\ProfileController;
|
|||||||
use Illuminate\Support\Facades\Route;
|
use Illuminate\Support\Facades\Route;
|
||||||
|
|
||||||
Route::get('/', function () {
|
Route::get('/', function () {
|
||||||
return view('welcome');
|
return view('jsonlint');
|
||||||
|
});
|
||||||
|
|
||||||
|
Route::get('/compare', function () {
|
||||||
|
return view('jsoncompare');
|
||||||
});
|
});
|
||||||
|
|
||||||
Route::get('/dashboard', function () {
|
Route::get('/dashboard', function () {
|
||||||
return view('dashboard');
|
return view('dashboard');
|
||||||
})->middleware(['auth', 'verified'])->name('dashboard');
|
})->middleware(['auth', 'verified'])->name('dashboard');
|
||||||
|
|
||||||
|
Route::get('/jsonlint', function () {
|
||||||
|
return view('jsonlint');
|
||||||
|
});
|
||||||
|
|
||||||
|
Route::get('/jsoncompare', function () {
|
||||||
|
return view('jsoncompare');
|
||||||
|
});
|
||||||
|
|
||||||
|
Route::get('/jsonschema', function () {
|
||||||
|
return view('jsonschema');
|
||||||
|
});
|
||||||
|
|
||||||
|
Route::get('/jsontemplates', function () {
|
||||||
|
return view('jsontemplates');
|
||||||
|
});
|
||||||
|
|
||||||
|
Route::get('/api', function () {
|
||||||
|
return view('api');
|
||||||
|
})->name('api');
|
||||||
|
|
||||||
Route::middleware('auth')->group(function () {
|
Route::middleware('auth')->group(function () {
|
||||||
Route::get('/profile', [ProfileController::class, 'edit'])->name('profile.edit');
|
Route::get('/profile', [ProfileController::class, 'edit'])->name('profile.edit');
|
||||||
Route::patch('/profile', [ProfileController::class, 'update'])->name('profile.update');
|
Route::patch('/profile', [ProfileController::class, 'update'])->name('profile.update');
|
||||||
|
|||||||
Reference in New Issue
Block a user