There was a small problem in our database. Some JSON data we kept in a column would sometimes have a string instead of an integer. Like {"tabSize": "5"} instead of {"tabSize": 5} of the like. Investigation on how that happened was just silly stuff like not calling parseInt on a value as it came off a <select> element in the DOM. This problem never surfaced because our Rails app just papered over it. But we’re moving our code to Go in when you parse JSON in Go, the struct type that you parse it out into needs to match those types perfectly, or else it panics. We had found that our Go code was working around this in all sorts of ways that felt sloppy and inconsistent.

One way to fix this? Fix any bad data going into the DB, then write a script to fix all the data in the DB. This is exactly the approach I took at first, and it would have absolutely fixed this problem.

But Alex took a step back and looked at the problem a bit wider, and we ended up building some tools that helped us solve this problem, and solve future problems related to this. For one, we built a more permission JSON parser that would not panic on something as easy to fix as a string-as-int problem. This worked by way of some Go reflection that could tell what types the data was supposed to be and coerce them if possible. But what should the value fall back to if it’s not savable? That was another tool we built to set the default values of Go structs to be potentially other values than what the defaults for their types are. And since this is all in the realm of data validation, we built another tool to validate the data in Go structs against constraints, so we can always keep the data they contain good.

Once all these tools were in place, the new script to fix the data was much easier to write. Just call the safe JSON function to fix the data and put it back. And the result is a cleaned up code base and tools we can use for data safety for the long term.

Time Jumps

  • 00:29 How can we fix the problem forever?
  • 03:53 Chris becomes a Go-pher
  • 15:04 Building rules that will work for anything, not just this situation
  • 18:54 Setting up proper testing is huge
  • 20:19 Sponsor: Intelligent Demand
  • 21:06 Testing for pointers
  • 25:18 Using GORM
  • 27:08 Supabase postgresDB

Sponsor: Split

This podcast is powered by Split. The Feature Management & Experimentation Platform that reimagines software delivery. By attaching insightful data to feature flags, Split frees you to quickly deploy, measure, and learn the impact of every feature you release. So you can safely deliver features up to 50 times faster and exhale. What a Release.

Start raising feature flags (and lowering stress). Visit Split.io/CodePen for a free trial.

Transcript

[Radio channel adjustment]

Announcer: Today, on CodePen Radio.

Chris Coyier: Hey, everybody. CodePen Radio #399. It's a founder podcast. I have Alex with me, Chris. What's up?

Alex Vazquez: What's up?!

Chris: Yeah. [Laughter] We've been working on some data manipulation stuff that ended up being kind of a funny story and exemplifies some Alex Vazquez thinking - if you ask me.

Alex: [Laughter] Good or bad?

Chris: Yeah. [Laughter] Yeah. Sometimes it ends up with a little more coding than you were ready for.

Alex: [Laughter]

Chris: But then it ends up being good in the end. You look back and are like, "God dang it! He was right again!" [Laughter]

Here's the crux of it. Before I tell you what the actual problem was, which I think has a funny beginning. The idea is that, to me, it's almost like a junior developer versus senior developer and beyond thinking. There's one way (if you have some problem in your code) that you can just immediately fix exactly what's sitting right in front of you and then call it done and walk away.

But perhaps a more experienced programmer like you--

[Laughter]

Chris: --will look at that problem and be like, "How did we get here, and how can we prevent it from happening again? What kind of tooling can we build? What more can we do to just solve the crap out of this problem?" [Laughter]

Alex: Right.

Chris: That's not just for this. That's a really generic statement about all kinds of stuff that we do around here. It's like, how can we fix the problem forever?

00:01:48

Alex: Right. One of my goals with everything we build is let's not just fix that problem but make it impossible for the problem to arise ever again (if at all possible). I always like to think about the idea, you know, programming is about making something work, but engineering is about making sure something doesn't fail. Whenever we can try to make sure something won't fail (now and into the future), that's a huge win for us because we're a small team and things always pop up. So, the more we can make the system safe for all our users the better.

Chris: Yeah. Super cool. That was the case here. And it also made me think about... Thinking about that, there's this book that we both read called Ender's Game. This genius child, he's a third. Remember, you're not supposed to have thirds in this future where there are too many people or something.

And there's this threat from Earth with these alien bugs that are coming to destroy Earth - or whatever. And it's a big problem, so they're looking for genius children to somehow lead because - I don't know - whatever - kids are good at video games, so maybe they can fly ships to kill bugs.

[Laughter]

Chris: The premise sounds ridiculous, but the book is great. So, there's this thing where he's like, "Okay, he's a genius, so we're going to send him up to flight school." But he's got all these problems. He's little, kind of, and there's this bully, and this bully is giving him crap.

And they get into a fight on the ship. And so, the one way to... Ender could just win this fight and walk away. The problem is, he's thinking, "Man, if I just win this fight, he's going to still be at school. He's going to be pissed off. He's going to come back at me, and there's going to be worse fights. This is going to be a big problem for me."

So, Ender's thinking in the book was, what if I really win this fight? What if I break the kid's arm? He's going to wash out. They're going to send him back to Earth, and I didn't just win this fight. I won every single fight going forward. You know? There'll never be another fight.

Alex: [Laughter]

Chris: A little bit abstract, maybe, but that was kind of the--

Alex: [Laughter] Yeah. That is the most interesting correlation to the problem that we solved.

Chris: Right.

Alex: I find that really interesting.

Chris: We're talking about JSON data now, so a little different than arm breaking.

Alex: Yeah, and I do have to say that Ender is a third and I also happen to be a third, so I'll let the audience draw their own conclusions on how else we may be similar.

Chris: Basically talking to a Wiggins here.

Alex: [Laughter]

00:00

00:04:18

Chris: This is the world's dumbest problem. I decided to learn... I don't know how this came to be, but I'm a Go-pher now, too. Dang right.

Alex: Official.

Chris: Yeah.

Alex: You've been deputized as an official Go-pher.

Chris: That's right. I was like, "How can I be more useful to CodePen?" Well, certainly, some more back-end stuff would be good, so I start writing a little bit of Go just this year. Well, I guess last year now because it's January.

I come to understand one particular... You know Go is typed, right? If it's an integer, it's an integer - or whatever. You better be sure about that. And if you have a piece of JSON--

You have types in Go, right? You say type and then - I don't know - let's call it font size. Font size is an integer. Then you have a chunk of JSON data, and you try to (what the call) unmarshal that data into a struct. On font size is a string in that JSON, but it's an integer in the type.

Guess what. It just panics. It just gives us. It just can't do it.

Unfortunately, we had that exact problem at CodePen. Some of our JSON data in our database -- some, not all, which is like, ugh, obnoxious--

Alex: Yeah.

Chris: --for various reasons.

Alex: I would say that's kind of the motto of the first six or seven years of CodePen. We get most of it right.

[Laughter]

Chris: Most, yeah. We're looking on the database. Sure as shit, there's font size "16" in the database. That will not unmarshal, and that's a problem because we're writing so much of our code in Go. We can't deal with that.

One of the ways to handle it in Go is just to be like, "Okay. It's not an integer. It's just an any or an empty interface," so that at least it will unmarshal and you'll get the data. Then later you can write more Go code to write a little switch statement to test what kind of data it actually is and coerce it into being the correct type. That's kind of fine, but that's real code smell, and that's something anybody in a PR would be like, "What the hell is that?!" [Laughter] You know?

Alex: Yeah.

Chris: That's no good. It just punts the problem down the road. Certainly, one of your mantras over the last several years has been, like, "No, no, no, no. We don't work for the data. The data works for us," or some variation of that, like, "Let's fix this problem where it started," which is kind of in the database and anywhere that might have put that bad data in the database.

So, that goes without saying. You fix where it's getting into the database wrong. In this case, it's something as simple as HTML. It's like when you get a number out of a select element in HTML.

In JavaScript, you've got to run parseint() on it to make sure it's an integer before you put it in the database. It was stuff like that that was putting the bad data in the database to begin with.

Of course, we fixed that. But then we were like, "Okay, let's solve the problem right in front of us. I'll write a script," because you've had me do this a couple of times now.

I wrote a script to fix some crap in the database (in the past). I'll just write a script. I'll loop over every single thing. I'll find instances where this is the case. I'll make sure it's an integer, and I'll put the data back.

I ended up writing a script like that, and then we were kind of like, "Hmm... But wait. I wonder what more we can do here." You know. Can we build some tools to make sure this never happens again and that we have more useful tools for ourselves?

I just did a lot of talking there, but does that about track for you?

00:07:37

Alex: Yeah, that was a big part of it when you submitted the pull request looking at it. One of the first things I noticed was there was a lot of classes that you were creating. Part of the reason you were creating the classes is to try to transfer from kind of a generic sting-type value to a more specific value. Then there was a lot of coercion between these types. But these types were oddly similar, right?

They looked almost exactly the same except for a few details amongst them. I think, in the end, there were close to four different struct types in our Golang code that were just all made to coerce bad data to good data and vice versa.

And so, just stepping back and looking at it, we're kind of still, I would say, in the middle of our transition from Rails code to Go code and transferring that logic. One of the things that we have on the Rails side is we have all these validators and ways to write logic that makes sure that code is in a certain format or meets a certain business requirement that we have. We didn't have that prior to this on the Go side.

So, if you could step back and look at this problem from a high level, not only are you looking at enforcing that the data type is correct, but you also should be enforcing that the actual value is one of the... That it meets kind of our business logic. For example, maybe the font size can't be smaller than 6 and it can't be larger than 40 or 50 - something like that. I don't know the exact rules that we have for font size, but--

Chris: Oh, I see what you mean. We're talking about it isn't just an integer but is an integer between these two integers because--

Alex: Right.

Chris: Yeah.

Alex: And maybe we don't allow odd numbers because we don't really like odd numbers in our font sizes.

Chris: Right. Sure.

Alex: Whatever logic we come up with, we wanted to make sure that we enforce that logic. And so, we took a step back and were like, "Okay. Not only are we just writing a bunch of code that is obvious that we're going to throw it away after the script is done, but then, on top of that, we don't have a standard way of enforcing this logic on the Go side."

Chris: Yes! That's right.

Alex: How would we do that? How do we do the coercion from JSON and marshalling and unmarshalling and just making sure that all of that logic is accurate across the board was kind of a big deal. Yeah, so we just kind of got to thinking about a solution.

00:10:20

Chris: Actually, now I remember that meeting because we were like, "We need to think of what exactly it is we're going to build." This was really squirrelly (at least in my mind).

We had one meeting that was really productive because we came up with three things that we needed. Number one, a way to unmarshal JSON safely and fix basic problems.

To me, this whole problem was JSON related, but it really wasn't. It was like what you described. It was about data validation and defaults too.

Number one was to unmarshal that data safely, and that's fine. We built that tool. Honestly, that's what we ended up using in the script, and it was super useful.

But then the second tool was validation. So, make sure that this integer is between these two values.

Then there was a third, and the third was what's the default. If you make a struct in Go, and you say, "This is an integer," the integer will be zero. Right?

Alex: Yeah.

Chris: I think zero is the default.

Alex: There's kind of like a default blank value. For strings, you get empty strings. Bullions, you get false.

Chris: False. Yeah.

Alex: On numeric values, you tend to get the zero value.

Chris: But we're like, "What if...?" What if you instantiate a new empty copy of this type struct? What if we don't want it to be false? We want it to be true.

What do you do? Well, go verbose. You make a new empty one, and then you just set it to true. You literally just write some code that sets it to true.

We did that all over in our codebase. We would be like, "Well, if this equals the default value." There'd be this big chunk of if/then code that would put the defaults that we wanted in there. There are all kinds of places we do that.

Instead, as I think you were looking around at validation libraries, one of them uses, in Go, when you're making a struct, you can put backtick characters after the type that just gives random extra information about what's going on there, and that's where one of these libraries would put validation info. If it's an integer, it'd say, "Well, the minimum is 4 and the maximum is 20."

Alex: Right, so you kind of stick the information in a Go tag, which is common for JSON and things like that.

Chris: A tag.

00:12:29

Alex: I think a lot of the influence of wanting to structure something like that is partially coming from the dynamic languages that we come from. Then also this want to have a declarative versus imperative solution. So, when we build our logic for data validation, like Chris said, if you always want a variable to be set to true because it's a bullion, you could just easily create a method that says, "New type name," and always return a new instance of that value. That's one way to do it.

Someone goes in with their own two hands and types in true, and that's all fine. But then when you go to the next one, you have to do the same thing. That's fine. It's not a big deal. We do that often where we kind of have these factory-type patterns, but they're just in methods.

But we were kind of looking. When you look at that and you're looking at many different structs with many different types and values and then they have to be part of an Enum, sometimes things get a little bit more complicated like the font size or the font family.

Let's say the font family can only be one of six font families that we support and blank value is just not really there. How do you enforce that concisely and consistently? And so, that's kind of where we ended up taking a library. It's called package validator and adapting it to our needs to using Go tags and using a lot of logic that they already have but then adapting it and making it our own. That's kind of the path that we ended up choosing to go.

But you can start to see how this problem that started with, "Hey, we need to make numeric or string-type values, integer-type values in JSON. Let's actually step back and look at how we do serialization, how we instantiate new objects in Go or new structs." Apologies. There's no objects in Go, just structs.

Chris: Mm-hmm.

Alex: Then how we enforce (for very simple logic), like, it should be part of an Enum. How do we enforce that?

We don't normally do that. That's a very rare occasion. But because we're in such a nascent part of our move to Go, there's still a fair amount of logic that we're moving from Ruby Rails.

Chris: Mm-hmm.

Alex: It was a good time to do that. It was like, okay, let's do that now because we'll get a lot of mileage out of a really great solution that we're happy with. And so, this felt like the most over-engineered solution for this particular problem, but the beauty is now we have something that's really reusable and something that we can build on.

00:15:26

Chris: That's what's wild about it because it's not just rules for this one struct. It's like this will work for anything, and we have all kinds of types, ones you'd expect like a user. But there are ones for, like, what a Pen is and what a processor is, and all these things.

To be able to say any one of those things, you can just put tags on the end of it, and you can say what are the defaults and what are the validation rules for it, and then use that as you will on any struct, which is powerful. That's exactly what made it complicated, too, because Go is just complicated in that way.

If you say, "Oh, loop over all the stuff in a struct," even that is a little more complicated than you would think it is, and it has to do with this Go concept of reflection. It's like, "I want to look at a copy of myself."

Alex: Yeah. To say that it's a little bit more complicated is an understatement on looping over structs and all the internal types of those structs was really, really interesting. It kind of gave us a whole new perspective on Go coding and meta coding in Go, which was really interesting.

Chris: Yeah. Wild. Yeah, they're not as simple. I'm sure, in JavaScript, there's just object.keys - or whatever - and you just loop over it.

Alex: [Laughter]

Chris: There's no object.keys in Go, unfortunately. I mean there kind of is, but it's--

Alex: Yeah, there is. It's just a lot more complex to wrap your mind around because of the typing. There are just certain things that you can't do in Go where you have to kind of dance around this stuff.

We actually, for a moment there, were almost considering putting this aside as too complicated for the moment.

Chris: Mm-hmm.

00:17:12

Alex: And so, it wasn't until I found a really, really great library from Mitchell Hashimoto, which is the CTO of HashiCorp. He has a really beautiful library. I think, if you're a Go coder, you should really look at the source code just to understand some of the things that he's doing there or consider using the library if you have this need.

He has a library called mapstructure, which is maps. It basically handles serialization when you're mapping things in Go and gives you the opportunity to set those values and provide custom functions if the value doesn't meet the type that you have. And he uses a lot of reflection in there, almost 100% reflection in the way he handles that.

I don't think we ended up... Ironically, we didn't end up using his library, but to say that I took inspiration from his library is almost an understatement. I ended up copying pieces of code to try to debug problems that I was having and then adapting it to what we were doing internally, and it really taught me a ton about Go, reflection in Go, and things like that. It was a really interesting path to just learn about the internals of how Go handles typing and things like that.

Then it does end up being a little scary in the sense of when you're handling Go reflection, a lot of it can panic at runtime, which is exactly one of the biggest draws of using Go.

Chris: Yeah, dude. Ugh!

Alex: It can be a bit frustrating at the beginning, for sure.

Chris: Yeah. Yeah. That stuff is rough because one little error... And I'm so used to Go being helpful and having big ol' red squiggles when your code is wrong because you're like, "That's not going to compile." But reflection doesn't know. It doesn't have enough information to know if it's going to panic or not, so you're back in the dark ages.

Alex: You're being dynamic in a language that's encouraging you to do the opposite. So, our solution definitely requires a fair amount of testing, and that's a big part of what we did. I think a huge part of what we did is we ended up creating all these structs to make sure that every scenario that we were even remotely interested in passed and was handled properly with our logic internally. That was a big, huge part of our solution was writing a bunch of tests with a bunch of random structs and things that we weren't even using at the time but we wanted to make sure that they would work because we're trying to kind of create a generic solution for all our structs now into the future. That was a big part of it.

Chris: Pretty good testing hygiene at CodePen. I'd say dang near every Go file has a little underscore test sitting right next to it, which is pretty cool.

00:20:20

[Guitar music starts]

Chris: This podcast is brought to you by Split, the feature management experimentation platform. What if a release was exactly how it sounds, a moment of relief? Ah... Escape from slow, painful deployments that hold back product engineers.

Free for teams and your features with Split. By attaching insightful data to feature flags, Split helps you quickly deploy, measure, and learn the impact of every feature you release, which means you can turn up what works, turn off what doesn't, and give software innovation the room to run wild.

Now you can safely deliver up to 50 times faster and exhale. Split feature management and experimentation, what a release. Reimagine software delivery. Start your free trial and create your first feature flag at split.io/codepen. Thanks so much for the support.

[Guitar music ends]

00:21:21

Chris: Part of that reflection stuff is also... I don't know to what degree this increases the complexity, but it seemed like, to me, a lot was, "Is this thing a pointer or not?"

Alex: [Laughter]

Chris: It just sucks. You know?

Alex: Yeah. Yeah, that is a huge part of Go. It's funny that you bring that up. I have a function that will get a pointer to a pointer to a pointer. It will--

I've ended up expanding on this reflection library to write kind of an internal logger library with it. Because of the... You can always have a pointer to a pointer. You can have... It's almost infinite. Just being able to dereference that safely, we've ended up coding with a bunch of little utilities internally where we're able to figure out, okay, is this thing the type that we think it is (in the end)?

A lot of that just came with the experience of actually using Golang reflection.

Chris: Mm-hmm.

Alex: Which was honestly pretty frustrating at the beginning because it just doesn't really seem to give you... It almost seems too generic, too abstract in order to go... It's a little hard to go from the Go code that you're used to writing to the Golang reflection code. It's very different. It's almost like a different paradigm.

Chris: Yeah because we weren't just inspecting these structs. The expectation is that we're changing. In fact, we called all these stuff data munging. Munging just being a weird ass word for manipulating data, essentially.

All of these functions are like munge JSON, munge to valid struct, mung struct. They all have munge in the name, which I don't mind because now I feel like we own that word, so these are our mungers, god'darn it.

Alex: [Laughter]

00:23:11

Chris: Whenever you do this, even this in Go, to me, was a very weird paradigm shift just basically because pointers exist. So, if you're already real comfortable with pointers from some other language, well, then good for you. But JavaScript doesn't really do the pointer thing, so there'll be some functions in Go can just return a value. They'll just have a return statement at the bottom. Then when you call that function, you can use the return value. That's very normal to me in JavaScript.

Although, one twist is that Go can return multiple things, which is weird. You know JavaScript can just return the one thing.

Alex: Right.

Chris: With Go, you can comma separate them, which is weird. But there's plenty of stuff in Go where you don't really use the return value. It just returns an error or nothing, and that's because what it expects is you to pass it with a pointer. Then because it's a pointer, inside that function, you can just mess with it. Then outside that code, it's been manipulated already. You know?

Alex: Right. Yeah. It breaks the conceptual model of a functional programming type function where the best functions take an input and provide an output because it makes them very easy to test.

Chris: Pure or whatever.

Alex: Yeah. They're purely functional. They don't have a side effect and, usually, those are the best types of functions in the sense of how easy they are to debug, test, and things like that.

But this throws all of that away. [Laughter] Not only are you throwing away some of the typing that you're used to with Go, but you're also throwing away this functional paradigm that we like to adhere to whenever we can. It's just really nice because it's easy to test.

You're literally testing a side effect on having passed in a pointer to a value, but that's just kind of the way Go handles serialization. When you call JSON marshal, that's what you're doing.

Chris: Right.

Alex: We kind of follow that paradigm. You don't want to be too weird in that Go code.

00:25:23

Chris: It kind of has to work that way because it doesn't know what type it is. That's the point is that it doesn't know. JSON, unmarshalling it doesn't know. Mungers, it doesn't know. I find it to be common.

We use some kind of ORM or something for dealing with our PostgreSQL. That's similar, too. If you're going to run a query and get some data back, it doesn't really know what type it is, so you've got to pass the pointer.

Alex: Yeah. There seems to be two major patterns in Go, which is like you're either generating Go code that's type safe, and we do plenty of that with gqlgen for GraphQL. Then what we're doing with GORM is the ORM that we're using - aptly named Go ORM.

They deal with everything through reflection, but there are other libraries. I think there's a library called SQLC or SQLX that people really love, and it generates type-safe Go code from your SQL. And so, those two paths seem to be the two common paths. You're either going to take the, "I'm going to pre-generate everything at build time and know that I'm type-safe," or "I'm going to use reflection at runtime."

There's a cost-benefit definitely to those two because that Go code that uses reflection is very dynamic but it will definitely panic at runtime if you do something incorrectly. So, you have to have a fair amount of testing with that.

Chris: Yeah.

Alex: Ironically, we use both. I can't say I would be 100% adhered to one or the other. For us, we just kind of use whatever works for that problem.

00:27:10

Chris: Yeah. I played with a Supabase the other day. Have you seen that company?

Alex: Mm-hmm. They're the open-source Firebase company.

Chris: Yeah, which is so weird. It's like in what way are you open-source Firebase? You're PostgreSQL and Firebase is their own little proprietary weird JSON store thing. So, that's not the same.

You kind of do real-time, but it's not your... I just don't understand why they are so obsessed with being a Firebase alternative when it's like you don't do the same stuff. But what they do is they'll spin up a PostgreSQL DB for you, like, instantly. Every single Supabase you spin up has a PostgreSQL in it.

I was like, "Oh, that's relevant to CodePen. That's cool." Then they don't have Go bindings, but I saw some... Like everything else in the world, there's supabase.go.

Alex: [Laughter] Yeah.

Chris: Some cool dude made Go bindings for it. If you're listening, Supabase, you should adopt and make official because it feels much nicer to use an official thing. Doesn't it? I think it feels weird.

Alex: Yeah. Whenever there's... I'm sure they get that all the time where you want the official library, but just narrow it down to JavaScript, TypeScript, and Go. If you could just stick to that, Supabase would really... [Laughter]

Chris: Yeah. Right.

Alex: That goes for every other vendor.

Chris: Nobody cares about your Python bindings, dude.

Alex: [Laughter]

Chris: Go it up.

Alex: Yeah.

Chris: [Laughter] Anyway, it was fun because those bindings are what you'd call an ORM, I guess, too. They're literally exactly like ours with GORM - or whatever. It's the same. It's the same crap. So, it was cool.

I wrote a Go cloud function, and that's what Netlify does, too. JavaScript, TypeScript, Go: that's all they've got.

Alex: Yeah, really. Good choice, Netlify.

Chris: Yeah. Yeah but it's funny. I feel like I was shortchanged because I just wrote a little cloud function, and it just connected to a Supabase and pulled some data - just to see if I could do it. And I totally did, and it felt very satisfying.

Captain Full Stack over here. You know? Really cool.

Alex: [Laughter] Yeah.

Chris: But Go was a second-class citizen the whole way through. No official bindings. Then, sure, you can write a Lambda in Go, but compared to what JavaScript is like, it's very second-class citizen, I felt like. It kind of sucked.

Go is awesome. Get on it, people. Stop making it second-class.

00:29:33

Alex: [Laughter] Only first class. Yeah, I'm sure it's a tough thing, right? You have to, a lot of times, pick languages that you're going to support directly and let the community try to kind of handle the rest. I'm sure the Rust folks would make an argument for their language.

Chris: Yeah. That's true.

Alex: So would... what's the Botany one?

Chris: Rust almost bugs me, you know?

Alex: Nim.

Chris: Oh, Nim? Like Bun or Zig?

Alex: Yeah, Nim. It's a little hard to pick languages that you're going to support directly when you're building these SDKs. For us, building in Go (like this), it's an interesting departure, and it's actually led to us building other tools internally like loggers and things like that and little niceties. But I can understand not wanting to build directly. If you're not building consistently in Go, it can be a little bit hairy.

Chris: Yeah. I could kind of get that. I mean you can only have so much expertise. You can't half-ass an ORM for a language you don't even know. That's not good either. [Laughter]

Alex: Yeah. Yeah. Just judging by how complex it was to solve the really corner case problems in our reflection logic, it was incredible. It was actually the most difficult thing I've ever done in Go was going through every data type and being able to map it correctly was something that was really complex.

And it was really interesting because I read a lot of open-source code to solve this problem, and there's a lot of -- I wouldn't say a lot, but there are quite a few Golang reflection libraries out there. None of them are comprehensive. They all introduce their own set of problems. I just thought it was just a really interesting problem out there.

Chris: It is interesting because there's only... It seems like a tight space. I don't know. There's something about it that feels weird to me to make an incomplete reflection library. There are only so many types, and they're either pointers or not. How is this not--?

Alex: [Laughter] Yeah. That's why I really got to highlight that map structure library. It doesn't sell itself as a generic or a better way to do reflection, but it was incredibly comprehensive (as far as I could tell).

Chris: Mm-hmm.

Alex: I ended up learning a lot more from it than actually attempting to use it. But yeah, to me it just speaks to how young the ecosystem still is, like Go being ten years old, and with the distribution that it has.

Chris: Mm-hmm.

Alex: Even though there's a ton, you know, there are millions of Go developers, it's still a very young, nascent language where parts of the ecosystem are not what you'd expects coming from a JavaScript/TypeScript world where there are at least ten packages that do exactly what you want and your choice is to pick the one that's best supported or best fits your API.

I think that sometimes when you're used to that, it is almost confusing to go into another language where you're like, "I can't believe you guys haven't solved this problem comprehensively with great docs and with at least three packages that have 5,000 stars on GitHub." [Laughter] You know? That's a big part of it, and so it's hard to get used to when you're dealing with such... you're used to such popular languages.

Chris: Yeah. Just consider that. We started with a string that shouldn't been an integer and ended up with the hardest Go code Alex has ever written.

[Laughter]

Chris: There you go.

Alex: Yeah.

Chris: But now we have a JSON munger and a struct munger and a struct default setter and a struct validator and all kinds of powerful tools for us going forward, which we are sure to benefit from and already have.

Alex: Yeah, and there's a fair amount of JSON in our future, I believe, from what I've heard.

[Laughter]

Alex: That's an understatement. We'll have more on that later.

Chris: Oh, my God. More on that later. All right. We'll talk to you all later. Remember, we're going to do an episode 400 and then take a short pause on this while we finish our God-given huge project that we're working on here.

Alex: Big project.

Chris: Then we'll talk about that.

Alex: Yep. Excited to talk about that. We'll talk about that for years.

Chris: Yeah.

Alex: It'll be amazing.

Chris: Yep. All right. See ya later.

Alex: Bye, y'all.

[Radio channel adjustment]