25.6 log/slog: Structured Logging Built Into the Standard Library

Finally. For years, logging in Go felt like we were all collectively duct-taping our fmt.Printf statements into something resembling a professional application. We’d bolt on logrus or zap, which are fantastic, but it created a fragmented ecosystem. The Go team, in their infinite and sometimes frustrating wisdom, decided it was time to bring order to the chaos. Enter log/slog in Go 1.21: structured logging, right there in the standard library.

25.5 Handling Dynamic JSON: json.RawMessage and map[string]any

Alright, let’s talk about the moment every Go developer faces: when your JSON isn’t a nice, tidy struct. It’s a moving target. Maybe it’s a third-party API that sends different object shapes based on some "type" field, or a config file with deeply nested, arbitrary blobs. You can’t define a static struct ahead of time, so you reach for the universal key: interface{}. Or, as it’s been mercifully renamed in Go 1.18, any.

25.4 Custom JSON Marshaling: MarshalJSON and UnmarshalJSON

Right, so you’ve hit the point where encoding/json’s default behavior just isn’t cutting it. Maybe you need to send data in a snake_case API but your structs are in Go’s CamelCase. Maybe you need to parse a date string that looks nothing like time.RFC3339. Or perhaps you need to marshal a struct into something that isn’t a JSON object for once. This is where you roll up your sleeves and implement the json.Marshaler and json.Unmarshaler interfaces. They’re your escape hatch from the library’s sometimes overly-opinionated defaults.

25.3 json.Encoder and json.Decoder: Streaming JSON

Alright, let’s get our hands dirty with the real workhorses of the encoding/json package: json.Encoder and json.Decoder. You’ve probably met json.Marshal and json.Unmarshal—they’re fine for small, self-contained jobs. But when you’re dealing with streams of data, whether it’s from an HTTP response body, a file on disk, or a network socket, the Marshal/Unmarshal duo starts to feel like using a sledgehammer to crack a nut. They need the whole nut in your hand at once.

25.2 Struct Tags: json:"name,omitempty" and json:"-"

Right, let’s talk about struct tags. You’ve probably seen these little string literals clinging to your struct fields like metadata remoras. They look like magic incantations, and honestly, they kind of are. The encoding/json package uses them to figure out how to map your beautifully named Go struct fields to the often-absurdly named keys in the JSON you’re marshaling or unmarshaling. Without them, you’re at the mercy of the encoder’s default behavior, which is about as subtle as a brick.

25.1 json.Marshal and json.Unmarshal: Basic Serialization

Alright, let’s get our hands dirty with the workhorses of Go’s JSON story: json.Marshal and json.Unmarshal. These two functions are your primary gateway between the structured, type-safe world of Go and the flexible, but loosey-goosey, world of JSON. They seem simple on the surface, but the devil—and the real power—is in the details. Think of Marshal as your meticulous packer. You give it a Go thing (a struct, a map, a slice), and it carefully wraps it up into a neat []byte parcel, ready to be shipped over the network or dumped into a file. Unmarshal is the unpacker on the other side. It takes that []byte parcel and, with a bit of guidance from you on what you expect to find inside, tries to reassemble it into a Go thing on your side.

6.6 Pattern Matching: LIKE, ILIKE, and SIMILAR TO

Alright, let’s talk about finding stuff. You’ve got a database full of text, and you need to pull out the rows where a column kind of matches what you’re looking for. Maybe you need everyone whose name starts with ‘Mc’, or all error codes that look like ‘404-%-something’. This is where we leave the clean, well-lit house of equality operators (=) and venture into the wild, sometimes messy, woods of pattern matching.

6.5 String Functions: length, substring, position, trim, upper, lower

Right, let’s talk about making your text do what you want. You’ve got strings in your database, and staring at them isn’t going to change them. You need tools. The functions we’re about to cover are your basic wrench and screwdriver set. They’re simple, they’re essential, and if you don’t know how to use them, you’re going to end up trying to hammer in a screw. I’ve seen it. It’s not pretty.

6.4 Database Encoding: UTF-8, SQL_ASCII, and Collations

Right, let’s talk about the alphabet soup of database text storage. You’ve probably already created a varchar column or two, but the real decisions—the ones that will either save your bacon or cause a multi-byte, accented-character-filled nightmare at 2 AM—happen at the encoding level. This isn’t academic; it’s the bedrock of storing anything beyond “Hello World.” Think of encoding as the dictionary your database uses to translate bits into letters. If you tell it to use an English dictionary (like LATIN1) and then try to store a Japanese character (こんにちは), it’s going to have a complete meltdown. The only sane choice for a modern application is UTF-8. It’s the one dictionary to rule them all, capable of storing pretty much every character from every human writing system, plus emojis. Yes, your database can store that pile of poop emoji (💩). You’re welcome.

6.3 char(n): Fixed-Length Strings and Padding Behavior

Now we arrive at the char(n) type, the most misunderstood and, frankly, most often misused character type in the SQL universe. Its defining characteristic is that it is fixed-length. When you define a column as char(10), every single value in that column is exactly 10 characters long. Always. No exceptions. The database engine enforces this with a zeal usually reserved for bouncers at an exclusive club. If you insert a string shorter than the defined length, say ‘cat’ into a char(10) column, PostgreSQL doesn’t just store those three characters. Oh no. It pads the string with spaces until it reaches the specified length. It becomes ‘cat ’ (with seven trailing spaces). When you retrieve that value, by default, the trailing spaces are stripped off, making it seem like the padding never happened. This is the source of much confusion and many a quiet bug.

6.2 varchar(n): Length-Limited Strings and When to Use Them

Alright, let’s talk about varchar(n). This is the data type you’ll use 99% of the time when you’re storing strings that aren’t the size of a small novel. Think of it as a string with a maximum length you get to define—the (n) part. It’s the “I know this field should be roughly this long, but I’m not a monster, I’ll let you use less space if you want” option. It’s the workhorse, and you need to understand its quirks, because they will bite you if you’re not careful.

6.1 text: The Unlimited Variable-Length Type

Right, let’s talk about text. This is the one you’ll use 95% of the time you need to store anything longer than a simple word or two. It’s PostgreSQL’s workhorse for storing strings of practically any length you can throw at it. Forget about pre-defining a maximum length; that’s the whole point. It’s a variable-length type, meaning it only takes up the space it needs (plus a tiny bit of overhead), which is exactly what you want most of the time.

— joke —

...