flatted

repository·main·Indexed 22 days ago

https://github.com/webreflection/flatted

A lightweight and fast circular JSON parser for serialization and deserialization of data structures containing circular references. Available as a JavaScript library (ESM and CJS) and a Go package, it provides an API compatible with the standard JSON object, including parse, stringify, toJSON, and fromJSON. It also includes a Go-based CLI tool for flattening and unflattening JSON strings.

Tokens
4.7K
Snippets
24
Records
25
Agent score
74%

What's inside flatted

  1. Correct usage pattern for Flatted vs JSON

    main

    To maintain data integrity, you must use flatted for both serialization and deserialization. Mixing JSON.parse with flatted.stringify (or vice versa) will break circular references and data integrity.

    Correct Pattern: flatted.parse(flatted.stringify(data))

    Incorrect Patterns:

    • JSON.parse(flatted.stringify(data))
    • flatted.parse(JSON.stringify(data))

    Note: flatted only serializes data compatible with the JSON standard. Internal classes or types not allowed by JSON will not be serialized as expected.

  2. How flatted handles circularity

    main

    During stringification, all Objects (including Arrays) and strings are flattened out and replaced with a unique index (represented as a string to avoid conflicts with numbers). During parsing, these indexes are replaced by the corresponding items from the flattened collection.

    // logic example
    var a = [{one: 1}, {two: '2'}];
    a[0].a = a;
    
    // Flatted.stringify(a) results in:
    // [["1","2"],{"one":1,"a":"0"},{"two":"3"},"2"]
  3. Implicitly survive JSON serialization with toJSON and fromJSON

    main

    To allow custom classes or complex objects to survive standard JSON.stringify and JSON.parse calls, implement toJSON() using flatted.toJSON() and a static fromJSON() method using flatted.fromJSON(). This pattern allows your objects to be serialized into a format that preserves circularity when processed by flatted.

    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
  4. Use flatted (Go) for circular JSON serialization

    main

    The flatted Go package provides a lightweight and fast way to serialize and parse JSON that contains circular references. Instead of failing like standard JSON encoders, flatted flattens the structure by replacing repeated or circular references with their index in the resulting array.

    To use it, define your data structures with standard json tags and use flatted.Stringify to convert the object to a string, and flatted.Parse to reconstruct the data into a generic map structure.

    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"}]
    
    	// Parse back into a generic map structure
    	res, _ := flatted.Parse(s)
    	aliceMap := res.(map[string]any)
    	fmt.Println(aliceMap["name"]) // Alice
    }
  5. Deserialize data with parse(flattedString)

    main

    The parse(flattedString) operation reconstructs the original data from a flatted array.

    Because standard JSON.parse cannot distinguish between a regular string and a string used as a reference index, flatted uses a specific technique during parsing:

    1. It uses a reviver function to wrap all strings in the array into new String() instances.
    2. During reconstruction, it checks if a value is an instanceof String.
    3. If it is, the value is treated as a directive to retrieve the actual data from the corresponding index in the input array.
    4. It uses a Set to track parsed objects and prevent infinite loops during reconstruction.
    // Example of the internal logic used for parsing
    const input = JSON.parse('[{"a":"1"},"b"]', Strings).map(strings);
    
    // convert strings primitives into String instances
    function Strings(key, value) {
      return typeof value === 'string' ? new String(value) : value;
    }
    
    // converts String instances into strings primitives
    function strings(value) {
      return value instanceof String ? String(value) : value;
    }
  6. Use flatted in ESM and CJS

    main

    The library can be imported as a regular module (ESM) or required as a CommonJS module. The primary API includes parse, stringify, toJSON, and fromJSON.

    // 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"}]
  7. Serialize data with stringify(any)

    main

    The stringify(any) operation converts a value into a flattedString. The resulting output is always an Array where index 0 contains the original value (or a reference to it).

    To handle circular references and repeated values, flatted replaces Array, Object, or string types with their stringified index from a internal Map. This ensures that every unique object, string, or array encountered is stored only once. Stringified indexes are used instead of numbers to prevent conflicts with regular numeric data.

    flatted.stringify('a');                     // ["a"]
    flatted.stringify(['a']);                   // [["1"],"a"]
    flatted.stringify(['a', 1, 'b']);           // [["1",1,"2"],"a","b"]
    flatted.stringify({a: 'a'});                // [{"a":"1"},"a"]
    flatted.stringify({a: 'a', n: 1, b: 'b'});  // [{"a":"1","n":1,"b":"2"},"a","b"]
  8. Convert a value to a flatted-compatible object with toJSON()

    main

    The toJSON(value) function converts a value into a plain object representation that is compatible with the flatted format. This is useful when you want to prepare an object for serialization without immediately producing a string.

    const flattedObject = toJSON(myCircularObject);
  9. Restore a flatted-compatible object with fromJSON()

    main

    The fromJSON(value) function takes a plain object (typically produced by toJSON) and restores its original structure, including circular references and complex links.

    const originalObject = fromJSON(flattedObject);