def dump_one_pipeline(json_dict, pipe_name, pipe_ptr, hlir, keep_pragmas=False):
    def get_table_name(p4_table):
        if not p4_table:
            return None
        return p4_table.name

    def table_has_counters(p4_table):
        for name, counter in hlir.p4_counters.items():
            if counter.binding == (p4.P4_DIRECT, p4_table):
                return True
        return False

    def table_direct_meters(p4_table):
        for name, meter in hlir.p4_meters.items():
            if meter.binding == (p4.P4_DIRECT, p4_table):
                return name
        return None

    pipeline_dict = OrderedDict()
    pipeline_dict["name"] = pipe_name
    pipeline_dict["id"] = dump_one_pipeline.pipeline_id
    dump_one_pipeline.pipeline_id += 1
    pipeline_dict["init_table"] = get_table_name(pipe_ptr)

    node_set = set()
    get_nodes(pipe_ptr, node_set)

    tables = []
    action_profiles = []
    for name, table in hlir.p4_tables.items():
        if table not in node_set:
            continue

        table_dict = OrderedDict()
        table_dict["name"] = name
        table_dict["id"] = dump_one_pipeline.table_id
        dump_one_pipeline.table_id += 1

        match_type = get_table_match_type(table)
        table_dict["match_type"] = match_type

        table_dict["type"] = get_table_type(table)
        if table_dict["type"] == "indirect" or\
           table_dict["type"] == "indirect_ws":
            table_dict["action_profile"] = table.action_profile.name
            dump_action_profile(pipe_name, action_profiles,
                                table.action_profile, keep_pragmas=keep_pragmas)

        table_dict["max_size"] = table.max_size if table.max_size else 16384

        # TODO(antonin): update counters to be the same as direct meters, but
        # that would make the JSON non-backwards compatible
        table_dict["with_counters"] = table_has_counters(table)

        table_dict["direct_meters"] = table_direct_meters(table)

        table_dict["support_timeout"] = table.support_timeout

        key = []
        for field_ref, m_type, mask in table.match_fields:
            key_field = OrderedDict()
            match_type = match_type_to_str(m_type)
            key_field["match_type"] = match_type

            if match_type != "valid":
                assert(isinstance(field_ref, p4.p4_field))
                field_width = field_ref.width
                if field_width == p4.P4_AUTO_WIDTH:
                    LOG_CRITICAL(
                        "Cannot match on field '{}' as matching on "
                        "variable-length fields is not supported".format(
                            field_ref))

            if match_type == "valid":
                if isinstance(field_ref, p4.p4_field):
                    header_ref = field_ref.instance
                else:
                    header_ref = field_ref
                assert(type(header_ref) is p4.p4_header_instance)
                key_field["target"] = header_ref.name
            else:
                key_field["target"] = format_field_ref(field_ref)

            if mask:
                if match_type == "valid":
                    LOG_WARNING("a field mask does not make much sense for a "
                                "valid match")
                    field_width = 1
                else:
                    assert(isinstance(field_ref, p4.p4_field))
                    field_width = field_ref.width
                # re-using this function (used by parser)
                mask = build_match_value([field_width], mask)
                LOG_INFO("you are using a mask in a match table, "
                         "this is still an experimental feature")
            else:
                mask = None  # should aready be the case
            key_field["mask"] = mask

            key.append(key_field)

        table_dict["key"] = key

        table_dict["actions"] = [a.name for a in table.actions]

        next_tables = OrderedDict()
        if "hit" in table.next_:
            next_tables["__HIT__"] = get_table_name(table.next_["hit"])
            next_tables["__MISS__"] = get_table_name(table.next_["miss"])
        else:
            for a, nt in table.next_.items():
                next_tables[a.name] = get_table_name(nt)
        table_dict["next_tables"] = next_tables

        # temporarily not covered by tests, because not part of P4 spec
        if hasattr(table, "default_action") and\
           table.default_action is not None:
            LOG_INFO("you are using the default_entry table attribute, "
                     "this is still an experimental feature")
            action, data = table.default_action
            default_entry = OrderedDict()
            for j_action in json_dict["actions"]:
                if j_action["name"] == action.name:
                    default_entry["action_id"] = j_action["id"]
            default_entry["action_const"] = True
            if data is not None:
                default_entry["action_data"] = [format_hexstr(i) for i in data]
                default_entry["action_entry_const"] = False
            table_dict["default_entry"] = default_entry

        # TODO: temporary, to ensure backwards compatibility
        if hasattr(table, "base_default_next"):
            table_dict["base_default_next"] = get_table_name(
                table.base_default_next)
        else:  # pragma: no cover
            LOG_WARNING("Your 'p4-hlir' is out-of-date, consider updating")

        if keep_pragmas:
            add_pragmas(table_dict, table)

        tables.append(table_dict)

    pipeline_dict["tables"] = tables
    pipeline_dict["action_profiles"] = action_profiles

    conditionals = []
    for name, cnode in hlir.p4_conditional_nodes.items():
        if cnode not in node_set:
            continue

        conditional_dict = OrderedDict()
        conditional_dict["name"] = name
        conditional_dict["id"] = dump_one_pipeline.condition_id
        dump_one_pipeline.condition_id += 1
        conditional_dict["expression"] = dump_expression(cnode.condition)

        conditional_dict["true_next"] = get_table_name(cnode.next_[True])
        conditional_dict["false_next"] = get_table_name(cnode.next_[False])

        if keep_pragmas:
            add_pragmas(conditional_dict, cnode)

        conditionals.append(conditional_dict)

    pipeline_dict["conditionals"] = conditionals

    return pipeline_dict