[32788f42] feat: implement database persistence, modernized UI with Tailwind, and Calendly-integrated QR card generator for Fotograf.de scraper

This commit is contained in:
2026-03-21 09:04:03 +00:00
parent 22fe4dbd9f
commit c02facdf5d
6975 changed files with 1835694 additions and 179 deletions

View File

@@ -0,0 +1,15 @@
ISC License
Copyright (c) 2018-2020, Andrea Giammarchi, @WebReflection
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.

View File

@@ -0,0 +1,111 @@
# flatted
[![Downloads](https://img.shields.io/npm/dm/flatted.svg)](https://www.npmjs.com/package/flatted) [![Coverage Status](https://coveralls.io/repos/github/WebReflection/flatted/badge.svg?branch=main)](https://coveralls.io/github/WebReflection/flatted?branch=main) [![License: ISC](https://img.shields.io/badge/License-ISC-yellow.svg)](https://opensource.org/licenses/ISC) ![WebReflection status](https://offline.report/status/webreflection.svg)
![snow flake](./flatted.jpg)
<sup>**Social Media Photo by [Matt Seymour](https://unsplash.com/@mattseymour) on [Unsplash](https://unsplash.com/)**</sup>
A super light (0.5K) and fast circular JSON parser, directly from the creator of [CircularJSON](https://github.com/WebReflection/circular-json/#circularjson).
Available also for **[PHP](./php/flatted.php)**.
Available also for **[Python](./python/flatted.py)**.
Available also for **[Go](./golang/README.md)**.
- - -
## JSON only values
If you need anything more complex than values JSON understands, there is a standard approach to recursion and more data-types than what JSON allows, and it's part of the [Structured Clone polyfill](https://github.com/ungap/structured-clone/#readme).
- - -
```js
npm i flatted
```
Usable via [CDN](https://unpkg.com/flatted) or as regular module.
```js
// ESM
import {parse, stringify, toJSON, fromJSON} from 'flatted';
// CJS
const {parse, stringify, toJSON, fromJSON} = require('flatted');
const a = [{}];
a[0].a = a;
a.push(a);
stringify(a); // [["1","0"],{"a":"0"}]
```
## toJSON and fromJSON
If you'd like to implicitly survive JSON serialization, these two helpers helps:
```js
import {toJSON, fromJSON} from 'flatted';
class RecursiveMap extends Map {
static fromJSON(any) {
return new this(fromJSON(any));
}
toJSON() {
return toJSON([...this.entries()]);
}
}
const recursive = new RecursiveMap;
const same = {};
same.same = same;
recursive.set('same', same);
const asString = JSON.stringify(recursive);
const asMap = RecursiveMap.fromJSON(JSON.parse(asString));
asMap.get('same') === asMap.get('same').same;
// true
```
## Flatted VS JSON
As it is for every other specialized format capable of serializing and deserializing circular data, you should never `JSON.parse(Flatted.stringify(data))`, and you should never `Flatted.parse(JSON.stringify(data))`.
The only way this could work is to `Flatted.parse(Flatted.stringify(data))`, as it is also for _CircularJSON_ or any other, otherwise there's no granted data integrity.
Also please note this project serializes and deserializes only data compatible with JSON, so that sockets, or anything else with internal classes different from those allowed by JSON standard, won't be serialized and unserialized as expected.
### New in V1: Exact same JSON API
* Added a [reviver](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse#Syntax) parameter to `.parse(string, reviver)` and revive your own objects.
* Added a [replacer](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify#Syntax) and a `space` parameter to `.stringify(object, replacer, space)` for feature parity with JSON signature.
### Compatibility
All ECMAScript engines compatible with `Map`, `Set`, `Object.keys`, and `Array.prototype.reduce` will work, even if polyfilled.
### How does it work ?
While stringifying, all Objects, including Arrays, and strings, are flattened out and replaced as unique index. `*`
Once parsed, all indexes will be replaced through the flattened collection.
<sup><sub>`*` represented as string to avoid conflicts with numbers</sub></sup>
```js
// logic example
var a = [{one: 1}, {two: '2'}];
a[0].a = a;
// a is the main object, will be at index '0'
// {one: 1} is the second object, index '1'
// {two: '2'} the third, in '2', and it has a string
// which will be found at index '3'
Flatted.stringify(a);
// [["1","2"],{"one":1,"a":"0"},{"two":"3"},"2"]
// a[one,two] {one: 1, a} {two: '2'} '2'
```

View File

@@ -0,0 +1,132 @@
'use strict';
/// <reference types="../types/index.d.ts" />
// (c) 2020-present Andrea Giammarchi
const {parse: $parse, stringify: $stringify} = JSON;
const {keys} = Object;
const Primitive = String; // it could be Number
const primitive = 'string'; // it could be 'number'
const ignore = {};
const object = 'object';
const noop = (_, value) => value;
const primitives = value => (
value instanceof Primitive ? Primitive(value) : value
);
const Primitives = (_, value) => (
typeof value === primitive ? new Primitive(value) : value
);
const resolver = (input, lazy, parsed, $) => output => {
for (let ke = keys(output), {length} = ke, y = 0; y < length; y++) {
const k = ke[y];
const value = output[k];
if (value instanceof Primitive) {
const tmp = input[+value];
if (typeof tmp === object && !parsed.has(tmp)) {
parsed.add(tmp);
output[k] = ignore;
lazy.push({ o: output, k, r: tmp });
}
else
output[k] = $.call(output, k, tmp);
}
else if (output[k] !== ignore)
output[k] = $.call(output, k, value);
}
return output;
};
const set = (known, input, value) => {
const index = Primitive(input.push(value) - 1);
known.set(value, index);
return index;
};
/**
* Converts a specialized flatted string into a JS value.
* @param {string} text
* @param {(this: any, key: string, value: any) => any} [reviver]
* @returns {any}
*/
const parse = (text, reviver) => {
const input = $parse(text, Primitives).map(primitives);
const $ = reviver || noop;
let value = input[0];
if (typeof value === object && value) {
const lazy = [];
const revive = resolver(input, lazy, new Set, $);
value = revive(value);
let i = 0;
while (i < lazy.length) {
// it could be a lazy.shift() but that's costly
const {o, k, r} = lazy[i++];
o[k] = $.call(o, k, revive(r));
}
}
return $.call({'': value}, '', value);
};
exports.parse = parse;
/**
* Converts a JS value into a specialized flatted string.
* @param {any} value
* @param {((this: any, key: string, value: any) => any) | (string | number)[] | null | undefined} [replacer]
* @param {string | number | undefined} [space]
* @returns {string}
*/
const stringify = (value, replacer, space) => {
const $ = replacer && typeof replacer === object ?
(k, v) => (k === '' || -1 < replacer.indexOf(k) ? v : void 0) :
(replacer || noop);
const known = new Map;
const input = [];
const output = [];
let i = +set(known, input, $.call({'': value}, '', value));
let firstRun = !i;
while (i < input.length) {
firstRun = true;
output[i] = $stringify(input[i++], replace, space);
}
return '[' + output.join(',') + ']';
function replace(key, value) {
if (firstRun) {
firstRun = !firstRun;
return value;
}
const after = $.call(this, key, value);
switch (typeof after) {
case object:
if (after === null) return after;
case primitive:
return known.get(after) || set(known, input, after);
}
return after;
}
};
exports.stringify = stringify;
/**
* Converts a generic value into a JSON serializable object without losing recursion.
* @param {any} value
* @returns {any}
*/
const toJSON = value => $parse(stringify(value));
exports.toJSON = toJSON;
/**
* Converts a previously serialized object with recursion into a recursive one.
* @param {any} value
* @returns {any}
*/
const fromJSON = value => parse($stringify(value));
exports.fromJSON = fromJSON;

View File

@@ -0,0 +1 @@
{"type":"commonjs"}

View File

@@ -0,0 +1 @@
self.Flatted=function(t){"use strict";const{parse:e,stringify:n}=JSON,{keys:r}=Object,o=String,s="string",c={},l="object",f=(t,e)=>e,i=t=>t instanceof o?o(t):t,a=(t,e)=>typeof e===s?new o(e):e,u=(t,e,n)=>{const r=o(e.push(n)-1);return t.set(n,r),r},p=(t,n)=>{const s=e(t,a).map(i),u=n||f;let p=s[0];if(typeof p===l&&p){const t=[],e=((t,e,n,s)=>f=>{for(let i=r(f),{length:a}=i,u=0;u<a;u++){const r=i[u],a=f[r];if(a instanceof o){const o=t[+a];typeof o!==l||n.has(o)?f[r]=s.call(f,r,o):(n.add(o),f[r]=c,e.push({o:f,k:r,r:o}))}else f[r]!==c&&(f[r]=s.call(f,r,a))}return f})(s,t,new Set,u);p=e(p);let n=0;for(;n<t.length;){const{o:r,k:o,r:s}=t[n++];r[o]=u.call(r,o,e(s))}}return u.call({"":p},"",p)},g=(t,e,r)=>{const o=e&&typeof e===l?(t,n)=>""===t||-1<e.indexOf(t)?n:void 0:e||f,c=new Map,i=[],a=[];let p=+u(c,i,o.call({"":t},"",t)),g=!p;for(;p<i.length;)g=!0,a[p]=n(i[p++],h,r);return"["+a.join(",")+"]";function h(t,e){if(g)return g=!g,e;const n=o.call(this,t,e);switch(typeof n){case l:if(null===n)return n;case s:return c.get(n)||u(c,i,n)}return n}};return t.fromJSON=t=>p(n(t)),t.parse=p,t.stringify=g,t.toJSON=t=>e(g(t)),t}({});

View File

@@ -0,0 +1 @@
const{parse:t,stringify:e}=JSON,{keys:n}=Object,o=String,r="string",s={},c="object",l=(t,e)=>e,f=t=>t instanceof o?o(t):t,i=(t,e)=>typeof e===r?new o(e):e,a=(t,e,n)=>{const r=o(e.push(n)-1);return t.set(n,r),r},u=(e,r)=>{const a=t(e,i).map(f),u=r||l;let p=a[0];if(typeof p===c&&p){const t=[],e=((t,e,r,l)=>f=>{for(let i=n(f),{length:a}=i,u=0;u<a;u++){const n=i[u],a=f[n];if(a instanceof o){const o=t[+a];typeof o!==c||r.has(o)?f[n]=l.call(f,n,o):(r.add(o),f[n]=s,e.push({o:f,k:n,r:o}))}else f[n]!==s&&(f[n]=l.call(f,n,a))}return f})(a,t,new Set,u);p=e(p);let r=0;for(;r<t.length;){const{o:n,k:o,r:s}=t[r++];n[o]=u.call(n,o,e(s))}}return u.call({"":p},"",p)},p=(t,n,o)=>{const s=n&&typeof n===c?(t,e)=>""===t||-1<n.indexOf(t)?e:void 0:n||l,f=new Map,i=[],u=[];let p=+a(f,i,s.call({"":t},"",t)),h=!p;for(;p<i.length;)h=!0,u[p]=e(i[p++],g,o);return"["+u.join(",")+"]";function g(t,e){if(h)return h=!h,e;const n=s.call(this,t,e);switch(typeof n){case c:if(null===n)return n;case r:return f.get(n)||a(f,i,n)}return n}},h=e=>t(p(e)),g=t=>u(e(t));export{g as fromJSON,u as parse,p as stringify,h as toJSON};

View File

@@ -0,0 +1,127 @@
/// <reference types="../types/index.d.ts" />
// (c) 2020-present Andrea Giammarchi
const {parse: $parse, stringify: $stringify} = JSON;
const {keys} = Object;
const Primitive = String; // it could be Number
const primitive = 'string'; // it could be 'number'
const ignore = {};
const object = 'object';
const noop = (_, value) => value;
const primitives = value => (
value instanceof Primitive ? Primitive(value) : value
);
const Primitives = (_, value) => (
typeof value === primitive ? new Primitive(value) : value
);
const resolver = (input, lazy, parsed, $) => output => {
for (let ke = keys(output), {length} = ke, y = 0; y < length; y++) {
const k = ke[y];
const value = output[k];
if (value instanceof Primitive) {
const tmp = input[+value];
if (typeof tmp === object && !parsed.has(tmp)) {
parsed.add(tmp);
output[k] = ignore;
lazy.push({ o: output, k, r: tmp });
}
else
output[k] = $.call(output, k, tmp);
}
else if (output[k] !== ignore)
output[k] = $.call(output, k, value);
}
return output;
};
const set = (known, input, value) => {
const index = Primitive(input.push(value) - 1);
known.set(value, index);
return index;
};
/**
* Converts a specialized flatted string into a JS value.
* @param {string} text
* @param {(this: any, key: string, value: any) => any} [reviver]
* @returns {any}
*/
export const parse = (text, reviver) => {
const input = $parse(text, Primitives).map(primitives);
const $ = reviver || noop;
let value = input[0];
if (typeof value === object && value) {
const lazy = [];
const revive = resolver(input, lazy, new Set, $);
value = revive(value);
let i = 0;
while (i < lazy.length) {
// it could be a lazy.shift() but that's costly
const {o, k, r} = lazy[i++];
o[k] = $.call(o, k, revive(r));
}
}
return $.call({'': value}, '', value);
};
/**
* Converts a JS value into a specialized flatted string.
* @param {any} value
* @param {((this: any, key: string, value: any) => any) | (string | number)[] | null | undefined} [replacer]
* @param {string | number | undefined} [space]
* @returns {string}
*/
export const stringify = (value, replacer, space) => {
const $ = replacer && typeof replacer === object ?
(k, v) => (k === '' || -1 < replacer.indexOf(k) ? v : void 0) :
(replacer || noop);
const known = new Map;
const input = [];
const output = [];
let i = +set(known, input, $.call({'': value}, '', value));
let firstRun = !i;
while (i < input.length) {
firstRun = true;
output[i] = $stringify(input[i++], replace, space);
}
return '[' + output.join(',') + ']';
function replace(key, value) {
if (firstRun) {
firstRun = !firstRun;
return value;
}
const after = $.call(this, key, value);
switch (typeof after) {
case object:
if (after === null) return after;
case primitive:
return known.get(after) || set(known, input, after);
}
return after;
}
};
/**
* Converts a generic value into a JSON serializable object without losing recursion.
* @param {any} value
* @returns {any}
*/
export const toJSON = value => $parse(stringify(value));
/**
* Converts a previously serialized object with recursion into a recursive one.
* @param {any} value
* @returns {any}
*/
export const fromJSON = value => parse($stringify(value));

View File

@@ -0,0 +1,60 @@
# flatted (Go)
A super light and fast circular JSON parser.
## Usage
```go
package main
import (
"fmt"
"github.com/WebReflection/flatted/golang/pkg/flatted"
)
type Group struct {
Name string `json:"name"`
}
type User struct {
Name string `json:"name"`
Friend *User `json:"friend"`
Group *Group `json:"group"`
}
func main() {
group := &Group{Name: "Developers"}
alice := &User{Name: "Alice", Group: group}
bob := &User{Name: "Bob", Group: group}
alice.Friend = bob
bob.Friend = alice // Circular reference
// Stringify Alice
s, _ := flatted.Stringify(alice)
fmt.Println(s)
// Output: [{"name":"Alice","friend":"1","group":"2"},{"name":"Bob","friend":"0","group":"2"},{"name":"Developers"}]
// Flattening in action:
// Index "0" is Alice, Index "1" is Bob, Index "2" is the shared Group.
// Parse back into a generic map structure
res, _ := flatted.Parse(s)
aliceMap := res.(map[string]any)
fmt.Println(aliceMap["name"]) // Alice
}
```
## CLI
Build the binary using the provided Makefile:
```bash
make build
```
Then use it to parse flatted JSON from stdin:
```bash
echo '[{"a":"1"},"b"]' | ./flatted
```

View File

@@ -0,0 +1,277 @@
package flatted
import (
"encoding/json"
"reflect"
"sort"
"strconv"
"strings"
)
// flattedIndex is a internal type used to distinguish between
// actual strings and flatted indices during the reconstruction phase.
type flattedIndex string
// Stringify converts a Go value into a specialized flatted JSON string.
func Stringify(value any, replacer any, space any) (string, error) {
knownKeys := []any{}
knownValues := []string{}
input := []any{}
index := func(v any) string {
input = append(input, v)
idx := strconv.Itoa(len(input) - 1)
knownKeys = append(knownKeys, v)
knownValues = append(knownValues, idx)
return idx
}
relate := func(v any) any {
if v == nil {
return nil
}
rv := reflect.ValueOf(v)
kind := rv.Kind()
if kind == reflect.String || kind == reflect.Slice || kind == reflect.Map || kind == reflect.Ptr {
for i, k := range knownKeys {
if kind == reflect.String {
if k == v {
return knownValues[i]
}
} else {
rk := reflect.ValueOf(k)
if rk.Kind() == kind && rk.Pointer() == rv.Pointer() {
return knownValues[i]
}
}
}
return index(v)
}
return v
}
transform := func(v any) any {
rv := reflect.ValueOf(v)
if !rv.IsValid() {
return nil
}
if _, ok := v.(json.Marshaler); ok {
return v
}
// Dereference pointers to process the underlying Slice, Map, or Array
for rv.Kind() == reflect.Ptr && !rv.IsNil() {
rv = rv.Elem()
}
switch rv.Kind() {
case reflect.Slice, reflect.Array:
res := make([]any, rv.Len())
for i := 0; i < rv.Len(); i++ {
res[i] = relate(rv.Index(i).Interface())
}
return res
case reflect.Map:
res := make(map[string]any)
keys := rv.MapKeys()
sort.Slice(keys, func(i, j int) bool {
return keys[i].String() < keys[j].String()
})
whitelist, isWhitelist := replacer.([]string)
for _, key := range keys {
kStr := key.String()
if isWhitelist {
found := false
for _, w := range whitelist {
if w == kStr {
found = true
break
}
}
if !found {
continue
}
}
res[kStr] = relate(rv.MapIndex(key).Interface())
}
return res
case reflect.Struct:
res := make(map[string]any)
t := rv.Type()
for i := 0; i < rv.NumField(); i++ {
field := t.Field(i)
if field.PkgPath != "" {
continue
}
name := field.Name
if tag := field.Tag.Get("json"); tag != "" {
name = strings.Split(tag, ",")[0]
}
res[name] = relate(rv.Field(i).Interface())
}
return res
default:
return v
}
}
index(value)
output := []any{}
for i := 0; i < len(input); i++ {
output = append(output, transform(input[i]))
}
var b []byte
var err error
indent := ""
if s, ok := space.(string); ok {
indent = s
} else if i, ok := space.(int); ok {
indent = strings.Repeat(" ", i)
}
if indent != "" {
b, err = json.MarshalIndent(output, "", indent)
} else {
b, err = json.Marshal(output)
}
if err != nil {
return "", err
}
return string(b), nil
}
// Parse converts a specialized flatted string into a Go value.
func Parse(text string, reviver func(key string, value any) any) (any, error) {
var jsonInput []any
if err := json.Unmarshal([]byte(text), &jsonInput); err != nil {
return nil, err
}
var wrap func(any) any
wrap = func(v any) any {
if s, ok := v.(string); ok {
return flattedIndex(s)
}
if arr, ok := v.([]any); ok {
for i, item := range arr {
arr[i] = wrap(item)
}
return arr
}
if m, ok := v.(map[string]any); ok {
for k, item := range m {
m[k] = wrap(item)
}
return m
}
return v
}
wrapped := make([]any, len(jsonInput))
for i, v := range jsonInput {
wrapped[i] = wrap(v)
}
input := make([]any, len(wrapped))
for i, v := range wrapped {
if fi, ok := v.(flattedIndex); ok {
input[i] = string(fi)
} else {
input[i] = v
}
}
if len(input) == 0 {
return nil, nil
}
value := input[0]
rv := reflect.ValueOf(value)
if rv.IsValid() && (rv.Kind() == reflect.Slice || rv.Kind() == reflect.Map) {
set := make(map[uintptr]bool)
set[rv.Pointer()] = true
res := loop(value, input, set)
if reviver != nil {
return revive("", res, reviver), nil
}
return res, nil
}
if reviver != nil {
return reviver("", value), nil
}
return value, nil
}
func revive(key string, value any, reviver func(k string, v any) any) any {
if arr, ok := value.([]any); ok {
for i, v := range arr {
arr[i] = revive(strconv.Itoa(i), v, reviver)
}
} else if m, ok := value.(map[string]any); ok {
keys := make([]string, 0, len(m))
for k := range m {
keys = append(keys, k)
}
sort.Strings(keys)
for _, k := range keys {
m[k] = revive(k, m[k], reviver)
}
}
return reviver(key, value)
}
func loop(value any, input []any, set map[uintptr]bool) any {
if arr, ok := value.([]any); ok {
for i, v := range arr {
if fi, ok := v.(flattedIndex); ok {
idx, _ := strconv.Atoi(string(fi))
arr[i] = ref(input[idx], input, set)
}
}
return arr
}
if m, ok := value.(map[string]any); ok {
for k, v := range m {
if fi, ok := v.(flattedIndex); ok {
idx, _ := strconv.Atoi(string(fi))
m[k] = ref(input[idx], input, set)
}
}
return m
}
return value
}
func ref(value any, input []any, set map[uintptr]bool) any {
rv := reflect.ValueOf(value)
if rv.IsValid() && (rv.Kind() == reflect.Slice || rv.Kind() == reflect.Map) {
ptr := rv.Pointer()
if !set[ptr] {
set[ptr] = true
return loop(value, input, set)
}
}
return value
}
// ToJSON converts a generic value into a JSON serializable object without losing recursion.
func ToJSON(value any) (any, error) {
s, err := Stringify(value, nil, nil)
if err != nil {
return nil, err
}
var res any
err = json.Unmarshal([]byte(s), &res)
return res, err
}
// FromJSON converts a previously serialized object with recursion into a recursive one.
func FromJSON(value any) (any, error) {
b, err := json.Marshal(value)
if err != nil {
return nil, err
}
return Parse(string(b), nil)
}

View File

@@ -0,0 +1,155 @@
self.Flatted = (function (exports) {
'use strict';
function _typeof(o) {
"@babel/helpers - typeof";
return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) {
return typeof o;
} : function (o) {
return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o;
}, _typeof(o);
}
/// <reference types="../types/index.d.ts" />
// (c) 2020-present Andrea Giammarchi
var $parse = JSON.parse,
$stringify = JSON.stringify;
var keys = Object.keys;
var Primitive = String; // it could be Number
var primitive = 'string'; // it could be 'number'
var ignore = {};
var object = 'object';
var noop = function noop(_, value) {
return value;
};
var primitives = function primitives(value) {
return value instanceof Primitive ? Primitive(value) : value;
};
var Primitives = function Primitives(_, value) {
return _typeof(value) === primitive ? new Primitive(value) : value;
};
var resolver = function resolver(input, lazy, parsed, $) {
return function (output) {
for (var ke = keys(output), length = ke.length, y = 0; y < length; y++) {
var k = ke[y];
var value = output[k];
if (value instanceof Primitive) {
var tmp = input[+value];
if (_typeof(tmp) === object && !parsed.has(tmp)) {
parsed.add(tmp);
output[k] = ignore;
lazy.push({
o: output,
k: k,
r: tmp
});
} else output[k] = $.call(output, k, tmp);
} else if (output[k] !== ignore) output[k] = $.call(output, k, value);
}
return output;
};
};
var set = function set(known, input, value) {
var index = Primitive(input.push(value) - 1);
known.set(value, index);
return index;
};
/**
* Converts a specialized flatted string into a JS value.
* @param {string} text
* @param {(this: any, key: string, value: any) => any} [reviver]
* @returns {any}
*/
var parse = function parse(text, reviver) {
var input = $parse(text, Primitives).map(primitives);
var $ = reviver || noop;
var value = input[0];
if (_typeof(value) === object && value) {
var lazy = [];
var revive = resolver(input, lazy, new Set(), $);
value = revive(value);
var i = 0;
while (i < lazy.length) {
// it could be a lazy.shift() but that's costly
var _lazy$i = lazy[i++],
o = _lazy$i.o,
k = _lazy$i.k,
r = _lazy$i.r;
o[k] = $.call(o, k, revive(r));
}
}
return $.call({
'': value
}, '', value);
};
/**
* Converts a JS value into a specialized flatted string.
* @param {any} value
* @param {((this: any, key: string, value: any) => any) | (string | number)[] | null | undefined} [replacer]
* @param {string | number | undefined} [space]
* @returns {string}
*/
var stringify = function stringify(value, replacer, space) {
var $ = replacer && _typeof(replacer) === object ? function (k, v) {
return k === '' || -1 < replacer.indexOf(k) ? v : void 0;
} : replacer || noop;
var known = new Map();
var input = [];
var output = [];
var i = +set(known, input, $.call({
'': value
}, '', value));
var firstRun = !i;
while (i < input.length) {
firstRun = true;
output[i] = $stringify(input[i++], replace, space);
}
return '[' + output.join(',') + ']';
function replace(key, value) {
if (firstRun) {
firstRun = !firstRun;
return value;
}
var after = $.call(this, key, value);
switch (_typeof(after)) {
case object:
if (after === null) return after;
case primitive:
return known.get(after) || set(known, input, after);
}
return after;
}
};
/**
* Converts a generic value into a JSON serializable object without losing recursion.
* @param {any} value
* @returns {any}
*/
var toJSON = function toJSON(value) {
return $parse(stringify(value));
};
/**
* Converts a previously serialized object with recursion into a recursive one.
* @param {any} value
* @returns {any}
*/
var fromJSON = function fromJSON(value) {
return parse($stringify(value));
};
exports.fromJSON = fromJSON;
exports.parse = parse;
exports.stringify = stringify;
exports.toJSON = toJSON;
return exports;
})({});

View File

@@ -0,0 +1 @@
self.Flatted=function(n){"use strict";function t(n){return t="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(n){return typeof n}:function(n){return n&&"function"==typeof Symbol&&n.constructor===Symbol&&n!==Symbol.prototype?"symbol":typeof n},t(n)}var r=JSON.parse,e=JSON.stringify,o=Object.keys,u=String,f="string",i={},c="object",a=function(n,t){return t},l=function(n){return n instanceof u?u(n):n},s=function(n,r){return t(r)===f?new u(r):r},y=function(n,t,r){var e=u(t.push(r)-1);return n.set(r,e),e},p=function(n,e){var f=r(n,s).map(l),y=e||a,p=f[0];if(t(p)===c&&p){var v=[],S=function(n,r,e,f){return function(a){for(var l=o(a),s=l.length,y=0;y<s;y++){var p=l[y],v=a[p];if(v instanceof u){var S=n[+v];t(S)!==c||e.has(S)?a[p]=f.call(a,p,S):(e.add(S),a[p]=i,r.push({o:a,k:p,r:S}))}else a[p]!==i&&(a[p]=f.call(a,p,v))}return a}}(f,v,new Set,y);p=S(p);for(var b=0;b<v.length;){var m=v[b++],g=m.o,h=m.k,O=m.r;g[h]=y.call(g,h,S(O))}}return y.call({"":p},"",p)},v=function(n,r,o){for(var u=r&&t(r)===c?function(n,t){return""===n||-1<r.indexOf(n)?t:void 0}:r||a,i=new Map,l=[],s=[],p=+y(i,l,u.call({"":n},"",n)),v=!p;p<l.length;)v=!0,s[p]=e(l[p++],S,o);return"["+s.join(",")+"]";function S(n,r){if(v)return v=!v,r;var e=u.call(this,n,r);switch(t(e)){case c:if(null===e)return e;case f:return i.get(e)||y(i,l,e)}return e}};return n.fromJSON=function(n){return p(e(n))},n.parse=p,n.stringify=v,n.toJSON=function(n){return r(v(n))},n}({});

View File

@@ -0,0 +1,80 @@
{
"name": "flatted",
"version": "3.4.2",
"description": "A super light and fast circular JSON parser.",
"unpkg": "min.js",
"main": "./cjs/index.js",
"scripts": {
"build": "npm run cjs && npm run rollup:esm && npm run rollup:es && npm run rollup:babel && npm run min && npm run test && npm run size",
"cjs": "ascjs esm cjs",
"rollup:es": "rollup --config rollup/es.config.js && sed -i.bck 's/^var /self./' es.js && rm -rf es.js.bck",
"rollup:esm": "rollup --config rollup/esm.config.js",
"rollup:babel": "rollup --config rollup/babel.config.js && sed -i.bck 's/^var /self./' index.js && rm -rf index.js.bck",
"min": "terser index.js -c -m -o min.js",
"size": "cat index.js | wc -c;cat min.js | wc -c;gzip -c9 min.js | wc -c;cat min.js | brotli | wc -c; cat es.js | brotli | wc -c; cat esm.js | brotli | wc -c",
"test": "c8 node test/index.js",
"test:php": "php php/test.php",
"test:py": "python python/test.py",
"ts": "tsc -p .",
"coverage": "mkdir -p ./coverage; c8 report --reporter=text-lcov > ./coverage/lcov.info"
},
"repository": {
"type": "git",
"url": "git+https://github.com/WebReflection/flatted.git"
},
"files": [
"LICENSE",
"README.md",
"cjs/",
"es.js",
"esm.js",
"esm/",
"index.js",
"min.js",
"php/flatted.php",
"python/flatted.py",
"golang/pkg/flatted/flatted.go",
"types/"
],
"keywords": [
"circular",
"JSON",
"fast",
"parser",
"minimal"
],
"author": "Andrea Giammarchi",
"license": "ISC",
"bugs": {
"url": "https://github.com/WebReflection/flatted/issues"
},
"homepage": "https://github.com/WebReflection/flatted#readme",
"devDependencies": {
"@babel/core": "^7.29.0",
"@babel/preset-env": "^7.29.0",
"@rollup/plugin-babel": "^7.0.0",
"@rollup/plugin-terser": "^1.0.0",
"@ungap/structured-clone": "^1.3.0",
"ascjs": "^6.0.3",
"c8": "^11.0.0",
"circular-json": "^0.5.9",
"circular-json-es6": "^2.0.2",
"flatted-view": "^0.1.1",
"jsan": "^3.1.14",
"rollup": "^4.59.0",
"terser": "^5.46.0",
"typescript": "^5.9.3"
},
"module": "./esm/index.js",
"type": "module",
"exports": {
".": {
"types": "./types/index.d.ts",
"import": "./esm/index.js",
"default": "./cjs/index.js"
},
"./esm": "./esm.js",
"./package.json": "./package.json"
},
"types": "./types/index.d.ts"
}

View File

@@ -0,0 +1,156 @@
<?php
/*!
* ISC License
*
* Copyright (c) 2018-2021, Andrea Giammarchi, @WebReflection
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
* OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
*/
class FlattedString {
public $value = '';
public function __construct($value) {
$this->value = $value;
}
}
class Flatted {
// public utilities
public static function parse($json, $assoc = false, $depth = 512, $options = 0) {
$input = array_map(
'Flatted::asString',
array_map(
'Flatted::wrap',
json_decode($json, $assoc, $depth, $options)
)
);
$value = &$input[0];
$set = array();
$set[] = &$value;
if (is_array($value))
return Flatted::loop(false, array_keys($value), $input, $set, $value);
if (is_object($value))
return Flatted::loop(true, Flatted::keys($value), $input, $set, $value);
return $value;
}
public static function stringify($value, $options = 0, $depth = 512) {
$known = new stdClass;
$known->key = array();
$known->value = array();
$input = array();
$output = array();
$i = intval(Flatted::index($known, $input, $value));
while ($i < count($input)) {
$output[$i] = Flatted::transform($known, $input, $input[$i]);
$i++;
}
return json_encode($output, $options, $depth);
}
// private helpers
private static function asString($value) {
return $value instanceof FlattedString ? $value->value : $value;
}
private static function index(&$known, &$input, &$value) {
$input[] = &$value;
$index = strval(count($input) - 1);
$known->key[] = &$value;
$known->value[] = &$index;
return $index;
}
private static function keys(&$value) {
$obj = new ReflectionObject($value);
$props = $obj->getProperties();
$keys = array();
foreach ($props as $prop)
$keys[] = $prop->getName();
return $keys;
}
private static function loop($obj, $keys, &$input, &$set, &$output) {
foreach ($keys as $key) {
$value = $obj ? $output->$key : $output[$key];
if ($value instanceof FlattedString)
Flatted::ref($obj, $key, $input[$value->value], $input, $set, $output);
}
return $output;
}
private static function relate(&$known, &$input, &$value) {
if (is_string($value) || is_array($value) || is_object($value)) {
$key = array_search($value, $known->key, true);
if ($key !== false)
return $known->value[$key];
return Flatted::index($known, $input, $value);
}
return $value;
}
private static function ref($obj, &$key, &$value, &$input, &$set, &$output) {
if (is_array($value) && !in_array($value, $set, true)) {
$set[] = $value;
$value = Flatted::loop(false, array_keys($value), $input, $set, $value);
}
elseif (is_object($value) && !in_array($value, $set, true)) {
$set[] = $value;
$value = Flatted::loop(true, Flatted::keys($value), $input, $set, $value);
}
if ($obj) {
$output->$key = &$value;
}
else {
$output[$key] = &$value;
}
}
private static function transform(&$known, &$input, &$value) {
if (is_array($value)) {
return array_map(
function ($value) use(&$known, &$input) {
return Flatted::relate($known, $input, $value);
},
$value
);
}
if (is_object($value)) {
$object = new stdClass;
$keys = Flatted::keys($value);
foreach ($keys as $key)
$object->$key = Flatted::relate($known, $input, $value->$key);
return $object;
}
return $value;
}
private static function wrap($value) {
if (is_string($value)) {
return new FlattedString($value);
}
if (is_array($value)) {
return array_map('Flatted::wrap', $value);
}
if (is_object($value)) {
$keys = Flatted::keys($value);
foreach ($keys as $key) {
$value->$key = self::wrap($value->$key);
}
}
return $value;
}
}
?>

View File

@@ -0,0 +1,144 @@
# ISC License
#
# Copyright (c) 2018-2025, Andrea Giammarchi, @WebReflection
#
# Permission to use, copy, modify, and/or distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
# copyright notice and this permission notice appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
# AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
# PERFORMANCE OF THIS SOFTWARE.
import json as _json
class _Known:
def __init__(self):
self.key = []
self.value = []
class _String:
def __init__(self, value):
self.value = value
def _array_keys(value):
for i in range(len(value)):
yield i
def _object_keys(value):
for key in value:
yield key
def _is_array(value):
return isinstance(value, (list, tuple))
def _is_object(value):
return isinstance(value, dict)
def _is_string(value):
return isinstance(value, str)
def _index(known, input, value):
input.append(value)
index = str(len(input) - 1)
known.key.append(value)
known.value.append(index)
return index
def _relate(known, input, value):
if _is_string(value) or _is_array(value) or _is_object(value):
try:
return known.value[known.key.index(value)]
except:
return _index(known, input, value)
return value
def _resolver(input, lazy, parsed):
def resolver(output):
keys = _array_keys(output) if _is_array(output) else _object_keys(output) if _is_object(output) else []
for key in keys:
value = output[key]
if isinstance(value, _String):
tmp = input[int(value.value)]
output[key] = tmp
if (_is_array(tmp) or _is_object(tmp)) and tmp not in parsed:
parsed.append(tmp)
lazy.append([output, key])
return output
return resolver
def _transform(known, input, value):
if _is_array(value):
output = []
for val in value:
output.append(_relate(known, input, val))
return output
if _is_object(value):
obj = {}
for key in value:
obj[key] = _relate(known, input, value[key])
return obj
return value
def _wrap(value):
if _is_string(value):
return _String(value)
if _is_array(value):
i = 0
for val in value:
value[i] = _wrap(val)
i += 1
elif _is_object(value):
for key in value:
value[key] = _wrap(value[key])
return value
def parse(value, *args, **kwargs):
json = _json.loads(value, *args, **kwargs)
wrapped = []
for value in json:
wrapped.append(_wrap(value))
input = []
for value in wrapped:
if isinstance(value, _String):
input.append(value.value)
else:
input.append(value)
value = input[0]
lazy = []
revive = _resolver(input, lazy, [value])
value = revive(value)
i = 0
while i < len(lazy):
o, k = lazy[i]
i += 1
o[k] = revive(o[k])
return value
def stringify(value, *args, **kwargs):
known = _Known()
input = []
output = []
i = int(_index(known, input, value))
while i < len(input):
output.append(_transform(known, input, input[i]))
i += 1
return _json.dumps(output, *args, **kwargs)

View File

@@ -0,0 +1,4 @@
export function parse(text: string, reviver?: (this: any, key: string, value: any) => any): any;
export function stringify(value: any, replacer?: (string | number)[] | ((this: any, key: string, value: any) => any), space?: string | number | undefined): string;
export function toJSON(value: any): any;
export function fromJSON(value: any): any;