Utils (@pim.sk/utils)

parse

Deep type parser. Converts string values to their native JavaScript types — recursively across arrays and objects. Handles null, undefined, booleans, numbers and date-like strings. Useful for normalizing raw form data or API responses.
import parse from '@pim.sk/utils/parse.mjs'

Usage

parse( value ) — primitives

Converts string representations of primitives to their native JS types. Handles null, undefined, true, false, NaN and numbers. Non-string inputs pass through unchanged.

parse( "null" )       // → null
parse( "undefined" )  // → undefined
parse( "true" )       // → true
parse( "false" )      // → false
parse( "NaN" )        // → NaN
parse( "42" )         // → 42
parse( "3.14" )       // → 3.14
parse( 42 )           // → 42   (number passes through)
parse( true )         // → true (boolean passes through)
parse( "null" )       // → null
parse( "true" )       // → true
parse( "42" )         // → 42
parse( 42 )           // → 42

parse( value ) — number strings

Converts numeric strings to numbers. Supports SK format (comma as decimal). Preserves strings that look like dates, phone numbers or GPS coordinates.

parse( "1 234" )            // → 1234       space as thousands separator
parse( "1,5" )              // → 1.5        SK decimal comma

// preserved as strings:
parse( "2024-01-15" )       // → "2024-01-15"    SQL date (2 dashes)
parse( "15.01.2024" )       // → "15.01.2024"    SK date  (2 dots)
parse( "48.1234,17.5678" )  // → "48.1234,..."   GPS (2+ dots)
parse( "v1.2.3" )           // → "v1.2.3"        contains letter

// phone — preserved:
parse( "+421888444333" )    // → "+421888444333"  leading + caught by regex
parse( "00421999555444" )   // → "00421999555444" roundtrip: 421...≠ 004...
parse( "0903555444" )       // → "0903555444"     roundtrip: 903...≠ 090...

// PSC (postal code) — ⚠ use ignore= for PSC fields in objects:
parse( "075 01" )           // → "07501"   space removed, leading 0 keeps string
parse( "125 52" )           // → 12552     ⚠ no leading 0 → converted to number
parse( "22512" )            // → 22512     ⚠ converted to number
parse( "1 234" )          // → 1234
parse( "1,5" )            // → 1.5

parse( "+421888444333" )  // → "+421888444333"   (leading +)
parse( "00421999555444" ) // → "00421999555444"  (leading 0, roundtrip protected)
parse( "0903555444" )     // → "0903555444"      (leading 0)

parse( "075 01" )         // → "07501"   ⚠ space removed, stays string
parse( "125 52" )         // → 12552     ⚠ converted to number
parse( "22512" )          // → 22512     ⚠ converted to number
// → use ignore="psc" when parsing objects with PSC fields

parse( array ) — recursive

Arrays are parsed recursively — every item is converted to its native type.

parse( ["1", "true", "null", "3,14", "2024-01-15", "hello"] )
// → [ 1, true, null, 3.14, "2024-01-15", "hello" ]
parse( ["1", "true", "null", "3,14", "2024-01-15", "hello"] )
// → [ 1, true, null, 3.14, "2024-01-15", "hello" ]

parse( object ) — recursive

Objects are parsed recursively — every value is converted to its native type. Useful for normalizing data from forms or API responses where all values arrive as strings.

const raw = {
    id:      "12",
    active:  "true",
    score:   "9,5",
    created: "2024-01-15",
    label:   "hello",
    empty:   "null",
}

parse( raw )
// → {
//     id:      12,
//     active:  true,
//     score:   9.5,
//     created: "2024-01-15",
//     label:   "hello",
//     empty:   null,
// }
parse({
    id:      "12",
    active:  "true",
    score:   "9,5",
    created: "2024-01-15",
    label:   "hello",
    empty:   "null",
})

parse — array of objects | nested objects

Recursion works at any depth. Arrays of objects and objects with sub-objects are fully traversed — every string value anywhere in the structure is converted.

// array of objects:
parse([
    { id: "1", active: "true",  score: "9,5" },
    { id: "2", active: "false", score: "7" },
])
// → [
//     { id: 1, active: true,  score: 9.5 },
//     { id: 2, active: false, score: 7   },
// ]

// nested object:
parse({
    count: "3",
    user: {
        id:     "42",
        active: "true",
        address: {
            zip:  "075 01",
            city: "Trebisov",
        }
    }
})
// → {
//     count: 3,
//     user: {
//         id: 42, active: true,
//         address: { zip: "07501", city: "Trebisov" }
//     }
// }
// array of objects
parse([
    { id: "1", active: "true",  score: "9,5" },
    { id: "2", active: "false", score: "7"   },
])

// nested object
parse({
    count: "3",
    user: { id: "42", active: "true",
        address: { zip: "075 01", city: "Trebisov" }
    }
})

parse( object, ignore )

Second argument is a comma-separated list of keys to skip. Ignored fields are left unchanged — their original string value is preserved.

// without ignore:
parse({ id: "42", code: "007", count: "3" })
// → { id: 42, code: "007", count: 3 }
//              ↑ "007" stays string — leading zero protected by isSafeNumber()

// with ignore — "id" kept as string:
parse({ id: "42", code: "007", count: "3" }, "id")
// → { id: "42", code: "007", count: 3 }

// multiple ignored keys:
parse({ id: "42", code: "007", count: "3" }, "id,code")
// → { id: "42", code: "007", count: 3 }
parse({ id: "42", code: "007", count: "3" })
// → { id: 42, code: "007", count: 3 }
//              ↑ leading zero preserved — roundtrip "007" → 7 → "7" ≠ "007"

parse({ id: "42", code: "007", count: "3" }, "id,code")
// → { id: "42", code: "007", count: 3 }

parse( object, ignore, booleans )

Third argument is a comma-separated list of keys that must be converted to boolean. String values are matched via regex /^true$|^1$|^yes$|^on$/i — only exact matches (case-insensitive) produce true, everything else produces false. Non-string values (number, bool, null…) go through boolean() cast.

// without booleans option — "1"/"0" become numbers:
parse({ active: "1", visible: "0" })
// → { active: 1, visible: 0 }

// with booleans — string matched via /^true$|^1$|^yes$|^on$/i:
parse({ active: "1",    visible: "0"   }, "", "active,visible")  // → true  / false
parse({ active: "true", visible: "yes" }, "", "active,visible")  // → true  / true
parse({ active: "on",   visible: "ON"  }, "", "active,visible")  // → true  / true
parse({ active: "TRUE", visible: "off" }, "", "active,visible")  // → true  / false

// non-string values go through boolean() cast:
parse({ active: 1,    visible: 0    }, "", "active,visible")     // → true  / false
parse({ active: true, visible: null }, "", "active,visible")     // → true  / false
// "1"/"0" → numbers without booleans param:
parse({ active: "1", visible: "0", score: "9" })
// → { active: 1, visible: 0, score: 9 }

// strings matched by /^true$|^1$|^yes$|^on$/i:
parse({ a: "1", b: "true", c: "yes", d: "on",
        e: "0", f: "false", g: "off", h: "no" }, "", "a,b,c,d,e,f,g,h")
// → { a: true, b: true, c: true, d: true,
//     e: false, f: false, g: false, h: false }

// non-string via boolean() cast:
parse({ active: 1, visible: 0, flag: null }, "", "active,visible,flag")
// → { active: true, visible: false, flag: false }
v 1.1.2