CertSafari
    Snowflake SnowPro Advanced: Data Analyst (DAA-C01)· Lessons

    Domain 2 · Lesson 8/19

    Querying JSON in Snowflake: VARIANT, Path Notation and FLATTEN

    Prepare different data types into a consumable format.

    11 min read
    4.6% of exam
    4 sources
    Published 5 Oct 2026
    Docs as of 4 Oct 2026

    What you will be able to do

    • Explain how VARIANT, OBJECT and ARRAY nest to hold semi-structured data
    • Convert JSON text to a queryable VARIANT with PARSE_JSON and predict its NULL and duplicate-key behaviour
    • Traverse JSON with colon, dot and bracket notation, casting results to SQL types
    • Expand nested arrays into rows with LATERAL FLATTEN and choose the right FLATTEN output column

    Key concept

    VARIANT as the container for semi-structured data — Snowflake stores hierarchical data such as JSON in a VARIANT, which can hold any value, including OBJECTs and ARRAYs whose elements are themselves VARIANTs. You query it with path operators and FLATTEN instead of defining a fixed schema first.

    1.Why semi-structured data lands in a VARIANT

    A relational table needs its schema defined before any data arrives. JSON doesn't work that way. New attributes can appear at any time, two records of the same kind can carry different keys, and values nest to any depth. The Snowflake documentation describes semi-structured data as data that "does not require a prior definition of a schema and can constantly evolve".

    Snowflake handles this with three data types that nest inside each other. An OBJECT is a set of key-value pairs, which the docs compare to a "dictionary" or "map". An ARRAY is an ordered list. A VARIANT can hold either of these, or any scalar. The two containers always hold VARIANTs, so a hierarchy is built like this: an OBJECT whose value is a VARIANT that wraps an ARRAY, whose cells are VARIANTs that wrap OBJECTs, and so on as deep as the data goes.

    You don't usually build this structure by hand. When you load a format Snowflake recognises and parses (JSON, Avro, Parquet or ORC), the data is converted into this internal representation automatically. You can store a whole document in one VARIANT column, or split chosen parts into separate typed columns. The usual pattern sets the column type to VARIANT and declares the input format in the file format of the COPY command:

    Declaring a VARIANT column and loading JSON into itsql
    CREATE TABLE my_table (my_variant_column VARIANT); COPY INTO my_table ... FILE FORMAT = (TYPE = 'JSON') ...

    Checkpoint 1 of 7· Check yourself

    A JSON document has a top-level array, and each cell holds an object with a nested array of readings. After you load it with TYPE = 'JSON', what type does Snowflake use for each element inside an ARRAY or OBJECT?

    Sources1

    2.Turning JSON text into a VARIANT with PARSE_JSON

    JSON often arrives as plain text: a VARCHAR column, a string from an API, or a value in a VALUES list. Path notation can't reach inside a string, so first you convert it. PARSE_JSON "Interprets an input string as a JSON document, producing a VARIANT value." TO_JSON goes the other way, from VARIANT back to a string. The two are only almost reciprocal, because TO_JSON doesn't preserve key order or whitespace.

    Two other edge cases come up in exam questions. First, SQL NULL and JSON null are different things. PARSE_JSON(NULL) returns SQL NULL, but PARSE_JSON('null') returns a valid VARIANT that contains a JSON null. Second, numbers are typed as they're parsed. A plain decimal keeps exact precision, "treating 123.45 as NUMBER(5,2), not as a DOUBLE value", while scientific notation becomes DOUBLE. JSON has no native date or timestamp, so those values stay strings until you convert them. The table below shows the TYPEOF results from the PARSE_JSON reference example.

    What PARSE_JSON produces for different input strings (TYPEOF of the result)
    Input stringTYPEOF result
    'null'NULL_VALUE (a JSON null inside a VARIANT)
    SQL nullNULL (SQL NULL)
    '123.12'DECIMAL
    '1.912e2'DOUBLE
    '[-1, 12, 289, 2188, false,]'ARRAY
    '{ "x" : "abc", "y" : false, "z": 10} 'OBJECT

    Duplicate keys are the last thing to watch for. By default PARSE_JSON is strict and rejects an object that repeats a key, with an error such as "duplicate object attribute". If you pass the optional 'd' parameter, the duplicates are accepted and only the last value for each repeated key is kept.

    Checkpoint 2 of 7· Fill the gap

    This insert parses an object that repeats key "a". Which parameter makes it succeed and keep "a": "789"?

    INSERT INTO vartab
    SELECT column1 AS n, PARSE_JSON(column2, ' ? ') AS v
      FROM VALUES (10, '{ "a" : "123", "b" : "456", "a": "789"} ')
         AS vals;

    Checkpoint 3 of 7· Check yourself

    A staging column holds the literal string 'null' in some rows. After PARSE_JSON, which statement is true for those rows?

    Sources2

    3.Traversing JSON with colon, dot and bracket notation

    Once the data is in a VARIANT, you navigate it with path operators. A colon joins the column to the first-level key, as in src:dealership. From there you go deeper with dot notation (src:salesperson.name) or bracket notation with single-quoted names (src['salesperson']['name']). The two notations return the same result.

    Dot notation: the salesperson name from each car_sales rowsql
    SELECT src:salesperson.name
        FROM car_sales
        ORDER BY 1;
    Bracket notation for the same pathsql
    SELECT src['salesperson']['name']
        FROM car_sales
        ORDER BY 1;

    There are three rules to remember. First, case: the column name is case-insensitive but element names are case-sensitive, so SRC:salesperson.name works and SRC:Salesperson.Name does not. Second, quoting: a key that isn't a valid SQL identifier, such as one containing a space or starting with a digit, must be wrapped in double quotes, as in src:"company name". Third, output type: the result is still a VARIANT, which is why the docs show values like "Frank Beasley" in double quotes. To get a plain SQL value for a consumable table, cast it, for example value:name::string.

    Checkpoint 4 of 7· Check yourself

    Which of these paths does NOT return the salesperson's name from the car_sales data?

    Checkpoint 5 of 7· Exam question

    An `orders` table has columns `order_id NUMBER` and `payload VARIANT`. Each payload looks like `{"customer":"Ana","items":[{"sku":"A1","qty":2},{"sku":"B7","qty":1}]}`. An analyst needs one output row per purchased item with `order_id`, `sku`, and `qty`. Which query does this?

    Sources3

    4.Expanding arrays into rows with LATERAL FLATTEN

    Path notation gives you one value per row. When a key holds an array, like the customer or vehicle arrays in car_sales, you usually want one row per element. That's what FLATTEN does. The docs call it "a table function that produces a lateral view of a VARIANT, OBJECT, or ARRAY column". With the LATERAL modifier, each output row is joined back to the source row it came from, so columns from the original table repeat once for each element.

    One row per customer, with VARIANT values cast to stringssql
    SELECT
      value:name::string as "Customer Name",
      value:address::string as "Address"
      FROM
        car_sales
      , LATERAL FLATTEN(INPUT => SRC:customer);

    FLATTEN always returns the same fixed set of columns. The query above reads VALUE and then applies path notation to it. For a single-level array, TABLE(FLATTEN(...)) and LATERAL FLATTEN(...) return the same result. For nested arrays, such as extras inside vehicle, you chain FLATTEN calls, and LATERAL is what lets each call reference the output of the one before. The MODE argument limits expansion to 'OBJECT', 'ARRAY' or 'BOTH' (the default). A recursive setting, off by default, expands every sub-element instead of only the one at the given path.

    FLATTEN output columns
    ColumnWhat it contains
    SEQA unique sequence number for the input record (not guaranteed gap-free or ordered)
    KEYFor objects or maps, the key of the exploded value
    PATHThe path to the element within the structure
    INDEXThe element's position if it is an array; otherwise NULL
    VALUEThe element of the flattened array or object
    THISThe element being flattened (useful in recursive flattening)

    Checkpoint 6 of 7· Match them up

    Match each FLATTEN output column to what it returns

    Tap a term, then the definition that fits it.

    Checkpoint 7 of 7· Exam question

    A `raw_events` table stores JSON in a `VARIANT` column named `raw`. Documents are stored as `{"UserId":42,"Country":"NL"}`. A dashboard query `SELECT raw:userid::NUMBER, raw:country::STRING FROM raw_events` returns NULL for every row, although the data is present. What explains this and fixes it?

    Sources34

    Exam traps

    Each one states something that sounds right. Open it to see what is actually true.

    1. 1.JSON paths are case-insensitive like the rest of Snowflake SQL, so src:Vehicle and src:vehicle are interchangeable.Why is that wrong?

      Only the column name is case-insensitive. JSON element names must match the key's case exactly.

      Covered in Traversing JSON with colon, dot and bracket notation

    2. 2.PARSE_JSON quietly keeps the last value when an object repeats a key.Why is that wrong?

      Strict mode is the default, and repeated keys raise an error. Only the 'd' parameter makes PARSE_JSON keep a single instance with the last value.

      Covered in Turning JSON text into a VARIANT with PARSE_JSON

    Sources

    Every claim above is drawn from one of these pages, quoted as it was written on the date shown.

    1. 1.
      “Semi-structured data does not require a prior definition of a schema and can constantly evolve”
      ↩︎ Why semi-structured data lands in a VARIANT
      “the data is converted to an internal data format that uses Snowflake VARIANT, ARRAY, and OBJECT data types.”
      ↩︎ Why semi-structured data lands in a VARIANT
      “A VARIANT can hold a value of any other data type, including an ARRAY or an OBJECT.”
      ↩︎ Key concept
      “An ARRAY or OBJECT holds a value of type VARIANT.”
      ↩︎ Checkpoint
    2. 2.
      “Interprets an input string as a JSON document, producing a VARIANT value.”
      ↩︎ Turning JSON text into a VARIANT with PARSE_JSON
      “treating 123.45 as NUMBER(5,2), not as a DOUBLE value”
      ↩︎ Turning JSON text into a VARIANT with PARSE_JSON
      “the returned object has a single instance of that key with the last value specified for that key.”
      ↩︎ Turning JSON text into a VARIANT with PARSE_JSON
      “By default, the function doesn’t allow duplicate keys in the JSON object”
      ↩︎ Exam trap 2
      “then the function returns NULL (rather than raising an error)”
      ↩︎ Prediction
      “if the input string is 'null', then it is interpreted as a JSON null value”
      ↩︎ Checkpoint
    3. 3.
      “Insert a colon : between the VARIANT column name and any first-level element”
      ↩︎ Traversing JSON with colon, dot and bracket notation
      “Operators : and subsequent . and [] always return VARIANT values containing strings.”
      ↩︎ Traversing JSON with colon, dot and bracket notation
      “then you must enclose the name in double quotes”
      ↩︎ Traversing JSON with colon, dot and bracket notation
      “FLATTEN is a table function that produces a lateral view of a VARIANT, OBJECT, or ARRAY column.”
      ↩︎ Expanding arrays into rows with LATERAL FLATTEN
      “the column name is case-insensitive but element names are case-sensitive”
      ↩︎ Exam trap 1
      “the column name is case-insensitive but element names are case-sensitive”
      ↩︎ Checkpoint
    4. 4.
      “use LATERAL so that each subsequent FLATTEN can reference the output of the previous one.”
      ↩︎ Expanding arrays into rows with LATERAL FLATTEN
      “the values in this input row are replicated to match the number of rows produced by FLATTEN.”
      ↩︎ Expanding arrays into rows with LATERAL FLATTEN
      “The index of the element, if it is an array; otherwise NULL.”
      ↩︎ Checkpoint

    Continue to page 2 of 2

    Preparing CSV, Parquet and XML Files for Querying in Snowflake

    Spotted a mistake, or was something unclear? Tell us.