Collect Recursive JSON Keys In Postgres

前端 未结 3 1033
借酒劲吻你
借酒劲吻你 2020-12-03 08:59

I have JSON documents stored in Postgres under the JSON data type (Postgres 9.3) and I need to recursively collect the key names down the tree.

For example, given th

相关标签:
3条回答
  • 2020-12-03 09:19

    I wrote a function to do this:

    CREATE OR REPLACE FUNCTION public.jsonb_keys_recursive(_value jsonb)
     RETURNS TABLE(key text)
     LANGUAGE sql
    AS $function$
    WITH RECURSIVE _tree (key, value) AS (
      SELECT
        NULL   AS key,
        _value AS value
      UNION ALL
      (WITH typed_values AS (SELECT jsonb_typeof(value) as typeof, value FROM _tree)
       SELECT v.*
         FROM typed_values, LATERAL jsonb_each(value) v
         WHERE typeof = 'object'
       UNION ALL
       SELECT NULL, element
         FROM typed_values, LATERAL jsonb_array_elements(value) element
         WHERE typeof = 'array'
      )
    )
    SELECT DISTINCT key
      FROM _tree
      WHERE key IS NOT NULL
    $function$;
    

    For an example, try:

    SELECT jsonb_keys_recursive('{"A":[[[{"C":"B"}]]],"X":"Y"}');
    

    Note that the other two answers don't find keys within objects inside arrays, my solution does. (The question didn't give any examples of arrays at all, so finding keys inside arrays may not have been what the original asker needed, but it was what I needed.)

    0 讨论(0)
  • 2020-12-03 09:31

    A little more concise version that you can just test with:

    WITH RECURSIVE reports (key, value) AS (
      SELECT
        NULL as key,
        '{"k1": {"k2": "v1"}, "k3": {"k4": "v2"}, "k5": "v3"}'::JSONB as value
    
    UNION ALL
    
       SELECT
        jsonb_object_keys(value)as key,
        value->jsonb_object_keys(value) as value
       FROM
        reports
       WHERE
        jsonb_typeof(value) = 'object'
    )
    
    SELECT
        *
    FROM
        reports;
    

    This will return a list that you then need to group with distinct.

    0 讨论(0)
  • 2020-12-03 09:34

    The trick is to add some final condition testing using json_typeof at the right place.

    You should also be using jsonb if you don't care about object key order.

    Here is my working environment:

    CREATE TABLE test (
      id  SERIAL PRIMARY KEY,
      doc JSON
    );
    
    INSERT INTO test (doc) VALUES ('{
     "files": {
      "folder": {
       "file1": {
        "property": "blah"
       },
       "file2": {
        "property": "blah"
       },
       "file3": {
        "property": "blah"
       },
       "file4": {
        "property": "blah",
        "prop" : {
          "clap": "clap"
        }
       }
     }
    },
    "software": {
      "apt": {
        "package1": {
            "version": 1.2
        },
        "package2": {
            "version": 1.2
        },
        "package3": {
            "version": 1.2
        },
        "package4": {
            "version": 1.2
        }
      }
     }
    }');
    

    The recursion is stopped when the second query does not return any rows. This is done by passing an empty object to json_each.

     WITH RECURSIVE doc_key_and_value_recursive(key, value) AS (
      SELECT
        t.key,
        t.value
      FROM test, json_each(test.doc) AS t
    
      UNION ALL
    
      SELECT
        t.key,
        t.value
      FROM doc_key_and_value_recursive,
        json_each(CASE 
          WHEN json_typeof(doc_key_and_value_recursive.value) <> 'object' THEN '{}' :: JSON
          ELSE doc_key_and_value_recursive.value
        END) AS t
    )
    SELECT *
    FROM doc_key_and_value_recursive
    WHERE json_typeof(doc_key_and_value_recursive.value) <> 'object';
    
    0 讨论(0)
提交回复
热议问题