File size: 6,637 Bytes
e706de2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
/**

 * Solution 2: JSON Parser Runnable

 *

 * This solution demonstrates:

 * - Graceful error handling with try-catch

 * - Configuration through constructor options

 * - Type checking input

 * - Providing default values

 */

import { Runnable } from '../../../../src/index.js';

/**

 * JsonParserRunnable - Safely parses JSON strings

 */
class JsonParserRunnable extends Runnable {
    constructor(options = {}) {
        super();

        // Store configuration
        this.defaultValue = options.defaultValue ?? null;
        this.throwOnError = options.throwOnError ?? false;
    }

    async _call(input, config) {
        // Ensure input is a string
        if (typeof input !== 'string') {
            if (this.throwOnError) {
                throw new Error('Input must be a string');
            }
            return this.defaultValue;
        }

        // Handle empty strings
        if (input.trim().length === 0) {
            return this.defaultValue;
        }

        // Try to parse JSON
        try {
            return JSON.parse(input);
        } catch (error) {
            // If throwOnError is true, propagate the error
            if (this.throwOnError) {
                throw new Error(`Failed to parse JSON: ${error.message}`);
            }

            // Otherwise, return default value
            return this.defaultValue;
        }
    }

    toString() {
        return `JsonParserRunnable()`;
    }
}

// ============================================================================
// Tests
// ============================================================================

async function runTests() {
    console.log('πŸ§ͺ Testing JsonParserRunnable Solution...\n');

    try {
        // Test 1: Valid JSON object
        console.log('Test 1: Valid JSON object');
        const parser = new JsonParserRunnable();
        const result1 = await parser.invoke('{"name":"Alice","age":30}');
        console.assert(result1.name === 'Alice', 'Should parse name');
        console.assert(result1.age === 30, 'Should parse age');
        console.log('βœ… Parsed:', result1);
        console.log();

        // Test 2: Valid JSON array
        console.log('Test 2: Valid JSON array');
        const result2 = await parser.invoke('[1, 2, 3, 4, 5]');
        console.assert(Array.isArray(result2), 'Should return array');
        console.assert(result2.length === 5, 'Should have 5 elements');
        console.log('βœ… Parsed:', result2);
        console.log();

        // Test 3: Invalid JSON returns null
        console.log('Test 3: Invalid JSON returns null');
        const result3 = await parser.invoke('this is not json');
        console.assert(result3 === null, 'Should return null for invalid JSON');
        console.log('βœ… Returns:', result3);
        console.log();

        // Test 4: Empty string returns null
        console.log('Test 4: Empty string returns null');
        const result4 = await parser.invoke('');
        console.assert(result4 === null, 'Should return null for empty string');
        console.log('βœ… Returns:', result4);
        console.log();

        // Test 5: With default value
        console.log('Test 5: With default value');
        const parserWithDefault = new JsonParserRunnable({
            defaultValue: { error: 'Invalid JSON' }
        });
        const result5 = await parserWithDefault.invoke('bad json');
        console.assert(result5.error === 'Invalid JSON', 'Should return default value');
        console.log('βœ… Returns:', result5);
        console.log();

        // Test 6: Nested JSON
        console.log('Test 6: Nested JSON');
        const nested = '{"user":{"name":"Bob","address":{"city":"NYC"}}}';
        const result6 = await parser.invoke(nested);
        console.assert(result6.user.address.city === 'NYC', 'Should parse nested objects');
        console.log('βœ… Parsed:', result6);
        console.log();

        // Test 7: Numbers and booleans
        console.log('Test 7: Primitive values');
        const result7a = await parser.invoke('42');
        const result7b = await parser.invoke('true');
        const result7c = await parser.invoke('"hello"');
        console.assert(result7a === 42, 'Should parse number');
        console.assert(result7b === true, 'Should parse boolean');
        console.assert(result7c === 'hello', 'Should parse string');
        console.log('βœ… Parsed primitives:', result7a, result7b, result7c);
        console.log();

        // Test 8: Batch processing
        console.log('Test 8: Batch processing');
        const inputs = [
            '{"id":1}',
            '{"id":2}',
            'invalid',
            '{"id":3}'
        ];
        const results = await parser.batch(inputs);
        console.log('   Inputs:', inputs);
        console.log('   Results:', results);
        console.assert(results[0].id === 1, 'First should parse');
        console.assert(results[2] === null, 'Invalid should be null');
        console.assert(results[3].id === 3, 'Last should parse');
        console.log('βœ… Batch processing works');
        console.log();

        // Test 9: throwOnError mode
        console.log('Test 9: throwOnError mode');
        const strictParser = new JsonParserRunnable({ throwOnError: true });
        try {
            await strictParser.invoke('invalid json');
            console.error('❌ Should have thrown error');
        } catch (error) {
            console.log('βœ… Throws error in strict mode:', error.message);
        }
        console.log();

        // Test 10: Complex real-world JSON
        console.log('Test 10: Complex real-world JSON');
        const complexJson = `{

      "users": [

        {"id": 1, "name": "Alice", "active": true},

        {"id": 2, "name": "Bob", "active": false}

      ],

      "metadata": {

        "total": 2,

        "timestamp": "2024-01-01"

      }

    }`;
        const result10 = await parser.invoke(complexJson);
        console.assert(result10.users.length === 2, 'Should have 2 users');
        console.assert(result10.metadata.total === 2, 'Should have metadata');
        console.log('βœ… Parsed complex JSON');
        console.log();

        console.log('πŸŽ‰ All tests passed!');
    } catch (error) {
        console.error('❌ Test failed:', error.message);
        console.error(error.stack);
    }
}

// Run tests
if (import.meta.url === `file://${process.argv[1]}`) {
    runTests();
}

export { JsonParserRunnable };