# How GeoIP Initializes Configuration from config.json or Byte Slices: A Deep Dive into the Instance Loader

> Discover how GeoIP initializes configuration from config.json or byte slices. Explore InitConfig and InitConfigFromBytes, dynamic converters, and plugin creators in this deep dive.

- Repository: [Loyalsoldier/geoip](https://github.com/loyalsoldier/geoip)
- Tags: deep-dive
- Published: 2026-03-06

---

**GeoIP loads configuration through the `Instance` type in [`lib/instance.go`](https://github.com/loyalsoldier/geoip/blob/main/lib/instance.go), using `InitConfig` for file or URL paths and `InitConfigFromBytes` for raw JSON, with both methods ultimately parsing content via `hujson.Standardize` and custom `UnmarshalJSON` implementations that dynamically instantiate input and output converters from registered plugin creators.**

The `loyalsoldier/geoip` project provides a flexible pipeline for converting GeoIP databases between various formats. Understanding how it handles initializing configuration from [`config.json`](https://github.com/loyalsoldier/geoip/blob/main/config.json) files or byte slices requires examining the `Instance` implementation and its plugin registration architecture. This article explores the complete flow from file reading to dynamic converter instantiation.

## The Instance Entry Points: InitConfig and InitConfigFromBytes

The core configuration loading logic resides in [`lib/instance.go`](https://github.com/loyalsoldier/geoip/blob/main/lib/instance.go), where the `Instance` type provides two public methods for initialization. According to the loyalsoldier/geoip source code, these methods form the primary API for configuration ingestion.

### Loading from Local Files and Remote URLs

The `InitConfig(configFile string) error` method (lines 36‑50) handles both local filesystem paths and remote HTTP(S) endpoints. The implementation detects URLs by checking for `http://` or `https://` prefixes. For local files, it uses `os.ReadFile` to read the content into memory. For remote resources, it delegates to `GetRemoteURLContent` to fetch the configuration over the network. Regardless of the source, the method ultimately converts the content to a byte slice and delegates to `InitConfigFromBytes`.

### Processing Raw JSON with hujson

The `InitConfigFromBytes(content []byte) error` method (lines 52‑71) serves as the central parsing engine. First, it invokes **`hujson.Standardize`** to strip JavaScript-style comments and trailing commas, transforming human-friendly JSON into strictly valid JSON. Then it unmarshals the cleaned content into the internal `config` struct using `json.Unmarshal`. This two-step process allows users to write configuration files with comments for documentation while maintaining standard JSON compatibility.

## Configuration Structure and Dynamic Unmarshaling

The `config` type defined in [`lib/config.go`](https://github.com/loyalsoldier/geoip/blob/main/lib/config.go) (lines 55‑58) serves as the root data structure:

```go
type config struct {
    Input  []*inputConvConfig  `json:"input"`
    Output []*outputConvConfig `json:"output"`
}

```

Each slice element implements a custom `UnmarshalJSON` method that enables dynamic plugin instantiation during the JSON parsing phase.

### Input Converter Initialization

The `inputConvConfig.UnmarshalJSON` method (lines 66‑91) handles the `input` array elements. During unmarshaling, it extracts the `type`, `action`, and `args` fields from the JSON object. It validates that the specified `action` exists in the global `ActionsRegistry`. Then it invokes **`createInputConfig`**, a factory function registered via `RegisterInputConfigCreator`, to obtain an `InputConverter` implementation. This approach decouples the configuration format from the concrete converter implementations.

### Output Converter Initialization

Similarly, the `outputConvConfig.UnmarshalJSON` method (lines 99‑127) processes the `output` array. This implementation defaults the `action` field to `ActionOutput` when the field is omitted in the JSON. It then calls **`createOutputConfig`**, registered via `RegisterOutputConfigCreator`, to construct the appropriate `OutputConverter`. Both unmarshaling methods perform case‑insensitive lookups in their respective registration caches.

## Plugin Registration System

The dynamic instantiation relies on two registration maps: `inputConfigCreatorCache` and `outputConfigCreatorCache`. These `map[string]func` tables are populated by each plugin's `init()` function through the **`RegisterInputConfigCreator`** and **`RegisterOutputConfigCreator`** helpers (lines 19‑44). When the configuration unmarshals, the system looks up the creator function by the `type` field (case‑insensitive) and invokes it to construct the concrete converter instances. The resulting converters are stored in the `Instance`'s internal `input` and `output` slices, ready for the conversion pipeline.

## Complete Configuration Loading Example

The following example demonstrates both file‑based and byte‑slice configuration initialization:

```go
package main

import (
    "log"
    "github.com/Loyalsoldier/geoip/lib"
)

func main() {
    // 1️⃣ Load configuration from a file on disk
    inst, err := lib.NewInstance()
    if err != nil {
        log.Fatal(err)
    }
    if err := inst.InitConfig("config.json"); err != nil {
        log.Fatal(err)
    }
    // Run the conversion pipeline
    if err := inst.Run(); err != nil {
        log.Fatal(err)
    }

    // 2️⃣ Load configuration from an in‑memory JSON blob (e.g. embedded or fetched elsewhere)
    jsonBlob := []byte(`{
        // comments are allowed
        "input": [{ "type": "maxmind_country_mmdb", "action": "input", "args": { "path": "GeoIP2-Country.mmdb" } }],
        "output": [{ "type": "plain_text", "action": "output", "args": { "file": "countries.txt" } }]
    }`)
    inst2, _ := lib.NewInstance()
    if err := inst2.InitConfigFromBytes(jsonBlob); err != nil {
        log.Fatal(err)
    }
    if err := inst2.Run(); err != nil {
        log.Fatal(err)
    }
}

```

## Summary

- **Dual Entry Points**: The `Instance` type provides `InitConfig` for file/URL paths and `InitConfigFromBytes` for raw byte slices, with both converging on the same parsing pipeline.
- **Comment Support**: The `hujson.Standardize` function preprocesses JSON to remove comments and trailing commas, allowing human-readable configuration files.
- **Dynamic Plugin Instantiation**: Custom `UnmarshalJSON` methods on `inputConvConfig` and `outputConvConfig` lookup and invoke registered creator functions to build concrete converters at runtime.
- **Registration Architecture**: Plugins register themselves via `RegisterInputConfigCreator` and `RegisterOutputConfigCreator`, storing factory functions in global cache maps for case‑insensitive lookup during configuration loading.

## Frequently Asked Questions

### Does GeoIP support JSON with comments in config.json?

Yes. As implemented in [`lib/instance.go`](https://github.com/loyalsoldier/geoip/blob/main/lib/instance.go), the `InitConfigFromBytes` method uses `hujson.Standardize` to strip both single-line and multi-line comments, as well as trailing commas, before passing the content to `json.Unmarshal`. This allows you to document your configuration inline without breaking the parser.

### How does GeoIP handle remote configuration files?

The `InitConfig` method detects remote URLs by checking for `http://` or `https://` prefixes. When detected, it fetches the content using `GetRemoteURLContent` rather than `os.ReadFile`, enabling centralized configuration management via HTTP endpoints before processing the bytes through the standard pipeline.

### What happens if an action type is not registered?

During unmarshaling in [`lib/config.go`](https://github.com/loyalsoldier/geoip/blob/main/lib/config.go), the `inputConvConfig.UnmarshalJSON` method validates that the specified `action` exists in the `ActionsRegistry`. If the action is not found, the unmarshaling process returns an error, preventing the initialization of unconfigured or mistyped converter pipelines.

### Can configuration be loaded from memory without a file?

Yes. The `InitConfigFromBytes` method accepts a `[]byte` slice directly, bypassing filesystem operations entirely. This enables scenarios such as embedded configuration assets, dynamically generated JSON, or configurations fetched through custom transport mechanisms not handled by the built-in URL fetcher.