stof 0.3.24

Stof is a unified data interface and interchange format for creating, sharing, and manipulating data. Stof removes the fragile and cumbersome parts of combining and using data in applications.
Documentation
//
// Copyright 2024 Formata, Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//    http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//


// Import the JSON file using the JSON format
// The explicit format is optional here, since the file extension is 'json'
import '../../json/tests/example.json' as self.example;

// Import the JSON file using the STOF format
// STOF is reverse compatable with JSON - valid JSON is always valid STOF
import stof 'src/json/tests/example.json' as self.stofexample;


#[test('SGML')]
fn sort_as(): str {
    let json_sort_as = self.example.glossary.GlossDiv.GlossList.GlossEntry.SortAs;
    let stof_sort_as = self.stofexample.glossary.GlossDiv.GlossList.GlossEntry.SortAs;

    assertEq(json_sort_as, stof_sort_as);
    return json_sort_as;
}

parse: {
    #[test]
    fn parse_json_string() {
        let json = '
        {
            "json": {
                "string": "hello",
                "number": 42,
                "boolean": true,
                "array": ["hello", 42, true, [false, 24], { "embedded": true }],
                "child": {
                    "is_child": true
                }
            }
        }';

        // Parse this JSON string using the 'json' format, placing the
        // newly created object in this object under the name 'parsed'
        parse(json, 'json', 'self.parsed');

        assertEq(typeof self.parsed, 'obj');
        assertEq(self.parsed.json.string, 'hello');
        assertEq(self.parsed.json.number, 42);
        assertEq(self.parsed.json.boolean, true);
        assertEq(self.parsed.json.child.is_child, true);

        let embedded = self.parsed.json.array.at(4);
        assertEq(embedded.embedded, true);

        // Now get rid of our parsed object
        drop self.parsed;
        assertEq(self.parsed, null);
        assertEq(self.parsed.json, null);
    }

    #[test]
    fn parse_json_with_stof() {
        let json = '
        {
            "json": {
                "string": "hello",
                "number": 42,
                "boolean": true,
                "array": ["hello", 42, true, [false, 24], { "embedded": true }],
                "child": {
                    "is_child": true
                }
            }
        }';

        // Parse this JSON string using the 'stof' format, placing the
        // newly created object in this object under the name 'parsed_stof'
        // Valid JSON is always valid STOF
        parse(json, 'stof', 'self.parsed_stof');

        assertEq(typeof self.parsed_stof, 'obj');
        assertEq(self.parsed_stof.json.string, 'hello');
        assertEq(self.parsed_stof.json.number, 42);
        assertEq(self.parsed_stof.json.boolean, true);
        assertEq(self.parsed_stof.json.child.is_child, true);

        let embedded = self.parsed_stof.json.array.at(4);
        assertEq(embedded.embedded, true);

        // Now get rid of our parsed object
        drop self.parsed_stof;
        assertEq(self.parsed_stof, null);
        assertEq(self.parsed_stof.json, null);
    }

    #[test]
    fn parse_json_with_stof_add_types() {
        let json = "
        {
            obj 'json': {
                /**
                 * Type info (optional).
                 * Will perform casts though when parsing.
                 */
                str string: +100,
                int number: +42; // Allow comments everywhere
                bool 'boolean': true; // Don't require line endings ',' or ';' accepted
                vec array: ['hello', 42, true, [false, 24,], { 'embedded': true; }] // trailing ',' in array
                obj child: {
                    bool 'is_child': true,
                }, // trailing comma in objects
            }
        }";

        // Parse this JSON string using the 'stof' format, placing the
        // newly created object in this object under the name 'parsed_types'
        // Valid JSON is always valid STOF
        parse(json, 'stof', 'self.parsed_types');

        assertEq(typeof self.parsed_types, 'obj');
        assertEq(self.parsed_types.json.string, '100');
        assertEq(self.parsed_types.json.number, 42);
        assertEq(self.parsed_types.json.boolean, true);
        assertEq(self.parsed_types.json.child.is_child, true);

        let embedded = self.parsed_types.json.array.at(4);
        assertEq(embedded.embedded, true);

        // Now get rid of our parsed object
        drop self.parsed_types;
        assertEq(self.parsed_types, null);
        assertEq(self.parsed_types.json, null);
    }
}

bytes: {
    test_json: {
        cool_factor: 100
        test: 'hello'
    }

    #[test]
    fn export_bytes() {
        let bytes = blobify(self.test_json, 'json'); // Will export a utf8 blob of JSON
        let json = bytes as str; // default cast from blob to str is utf8
        
        // Now parse the bytes into ourselves as the object 'tmpjson'
        // This is effectively copying 'test_json' above, placing it in parallel as 'tmpjson'
        parse(bytes, 'json', 'self.tmpjson');
        let parsed_json = stringify(self.tmpjson, 'json'); // turned back into a string
        
        // Now drop our temp object now that we have a string - deletes our copy from the doc
        drop self.tmpjson;

        assertEq(self.tmpjson, null);
        assertEq(parsed_json, json);
        assertEq(json, '{"cool_factor":100,"test":"hello"}');
    }
}

#[test]
fn import_nd_json() {
    let ndjson = '{"first": true}\n{"second": true}\n{"third": true}';
    parse(ndjson, 'ndjson', 'self.ndjson.import');
    
    assertEq(self.ndjson.import.first, true);
    assertEq(self.ndjson.import.second, true);
    assertEq(self.ndjson.import.third, true);
}