PHP vs GoLang : JSON Decoding

Mar. 11, 2021

Handling JSON data is a pretty common thing in web development, either you need to accept some JSON data or you need to send some JSON data to the end-user.

PHP

If your background is PHP then you usually handle the JSON data as

$jsonData = `{"id" : 100}`;
$decodeJson = json_decode($jsonData);

echo "My id is:" . $decodeJson->id;

The above example will decode the JSON into an object and you will be able to get the value of an ID.

Pretty simple and straightforward, the only downside of this is that you don’t actually know if the ID key was part of the JSON string. So if you had an endpoint that requires the ID key, you will most likely need to add an isset() check to determine that the key exists in the decoded JSON.

$jsonData = `{"id" : 100}`;
$decodeJson = json_decode($jsonData);

if (!isset($decodeJson->id)) {
	return echo "Error, the ID parameter is required!";
}

echo "My id is:" . $decodeJson->id;

Golang

Golang by default has the ability to decode JSON, by using the core “encoding/json” package, but it is a little different from PHP, because it’s a statically typed language, which means that it needs to know the data types.

There are 2 ways of decoding the JSON, either you decode JSON to a struct or you decode JSON to a map.

Decode JSON to struct

This method is useful if you know what kind of data you have. For example, if you have an API endpoint that accepts a JSON string with a single key “ID” that holds an integer.

First you need to define your struct. You can think of it as a schema file for you JSON.

package main

type Identifier struct {
	Id  int      `json:"id"`
}

This will tell the JSON package, which data types you are expecting and want to decode/save. You can now use this struct in your main function.

package main

import (
	"encoding/json"
	"fmt"
)

type Identifier struct {
	Id int `json:"id"`
}

func main() {

	jsonData := `{"id" : 100}`

	decodedJson := Identifier{}

	// json.Unmarshal() is the equivalent of json_decode().
	// The first parameter is the JSON data which needs to be a []byte type.
	// The second parameter is the variable(struct), that will hold the decoded data.
	err := json.Unmarshal([]byte(jsonData), &decodedJson)
	if err != nil {
		panic(err)
	}

	fmt.Printf("My ID is: %d", decodedJson.Id)
}

When you run your code, the json.Unmarshal will grab the struct definition and decode the JSON string into a new variable called decodedJson.

It will also check the types, so if the ID is not an integer, then an error will be thrown.

What is great about this is that it will also throw away any non-defined JSON data.

package main

import (
	"encoding/json"
	"fmt"
)

type Identifier struct {
	Id int `json:"id"`
}

func main() {

	jsonData := `{"id" : 100, "appKey": "23dfdfsdf3434"}`

	decodedJson := Identifier{}

	err := json.Unmarshal([]byte(jsonData), &decodedJson)
	if err != nil {
		panic(err)
	}

	fmt.Printf("Id is: %d", decodedJson.Id)
}

This will also work, you will be able to get the ID number, but the ‘appKey’ will not be decoded and will be ignored.

If you want to also get the ‘appKey’ then you will need to add it to the struct definition.

package main

import (
	"encoding/json"
	"fmt"
)

type Identifier struct {
	Id     int    `json:"id"`
	AppKey string `json:"appKey"`
}

func main() {

	jsonData := `{"id" : 100, "appKey": "23dfdfsdf3434"}`

	decodedJson := Identifier{}

	err := json.Unmarshal([]byte(jsonData), &decodedJson)
	if err != nil {
		panic(err)
	}

	fmt.Printf("Id is: %d and my appKey is: %s", decodedJson.Id, decodedJson.AppKey)
}

This is a really powerful feature and there are quite a lot of use-cases for this. But sometimes, you actually don’t know what data you will get in JSON string or maybe you need to get all the data and perform some calculations. For this, you will need to decode the JSON into a map.

Decode JSON to map

This approach is helpful if you don’t know the exact data types of your JSON data.

package main

import (
	"encoding/json"
	"fmt"
)

func main() {
	var decodedJson map[string]interface{}
	
	jsonData := `{"id" : 100}`

	err := json.Unmarshal([]byte(jsonData), &decodedJson)
	if err != nil {
		panic(err)
	}
   
  // Even though ID is an integer, the json.Unmarshal() will always
  // transform the integers to float64. 
  // When decoding as a struct, golang does the type conversion for you.
	fmt.Printf("Id is: %v", decodedJson["id"].(float64))
}

In the above example, we create a new map variable, which will store the decoded JSON. Then use the same json.Unmarshal() method as in the previous example.

The decoded JSON is now stored in the decodeJson variable, which is an interface{}. In short, an interface value can hold any type, so for example you can have an interface with a value of integer and string.

The trick is when you want to display the data because you will need to define the type. In the example above we assume that the ID is of type float64, but that is not necessary. If this would be an endpoint, then we can’t really be sure that the ID key will always be of type float64, so additional checks would need to be added, similar to what we did with isset() in PHP.

package main

import (
	"encoding/json"
	"fmt"
)

func main() {
	var decodedJson map[string]interface{}

	jsonData := `{"id" : 100}`

	err := json.Unmarshal([]byte(jsonData), &decodedJson)
	if err != nil {
		panic(err)
	}

	if _, ok := decodedJson["id"].(float64); ok {
		fmt.Printf("Id is: %v", decodedJson["id"].(float64))
	} else {
		panic(fmt.Sprintf("Error, the ID parameter is an invalid type"))
	}
}

If none of the above ways suits your needs, then you can always count on the community.

There are 3rd party packages that can handle decoding JSON data better / simpler, one of them isĀ gjson, which gives you a really quick way to get values from a JSON string by specifying a path.

package main

import (
	"fmt"
	"github.com/tidwall/gjson"
)

func main() {

	jsonData := `{"id" : 100}`

	decodedJson := gjson.Get(jsonData, "id")
	fmt.Printf("Id is: %v", decodedJson.Int())
}

The examples I used are pretty straightforward and easy, usually, the JSON string will be more complex but I hope this gives you a bit more clear comparison between the two languages and how to handle JSON decoding.