SoFunction
Updated on 2025-03-05

Golang parses JSON and its solution

Write in front

When writing Go, serialization and deserialization are often used to record the pitfalls encountered.

The null pointer will be parsed into the string "null"

type Person struct {
	Name string
	Age  int
}

func main() {
	var p *Person
	bytes, err := (p)
	checkError(err)
	("len:%d, result:%s\n", len(bytes), string(bytes))  // len:4, result:null
}

func checkError(err error) {
	if err != nil {
		("err:%+v\n", err)
	}
}

When a null pointer is a "null" string, I thought it was a "" or an error was reported.

There is another strange pit

type Person struct {
	Name string
	Age  int
}

func main() {
	var p *Person
	s := `null`
	err := ([]byte(s), &p)
	checkError(err)
	("p:%+v\n", p) // p:<nil>
}

This does not report an error, but gets a null pointer p

If you change s to other stringss := "abc", an error is reported:invalid character 'a' looking for beginning of valueWhat I understood before wasnullFor Go, it should followabcThere is no difference, it's all strings. I didn't expect that they are different. Let's dig into the underlying code.

Before UnMarshal it had acheckValidfunction

func checkValid(data []byte, scan *scanner) error {
	()
	for _, c := range data {
		++
		if (scan, c) == scanError {
			return 
		}
	}
	if () == scanError {
		return 
	}
	return nil
}

checkValidThe function will check every character and call the step function. The initial step value isstateBeginValue

// stateBeginValue is the state at the beginning of the input.
func stateBeginValue(s *scanner, c byte) int {
	if isSpace(c) {
		return scanSkipSpace
	}
	switch c {
	case '{':
		 = stateBeginStringOrEmpty
		return (c, parseObjectKey, scanBeginObject)
	case '[':
		 = stateBeginValueOrEmpty
		return (c, parseArrayValue, scanBeginArray)
	case '"':
		 = stateInString
		return scanBeginLiteral
	case '-':
		 = stateNeg
		return scanBeginLiteral
	case '0': // beginning of 0.123
		 = state0
		return scanBeginLiteral
	case 't': // beginning of true
		 = stateT
		return scanBeginLiteral
	case 'f': // beginning of false
		 = stateF
		return scanBeginLiteral
	case 'n': // beginning of null
		 = stateN
		return scanBeginLiteral
	}
	if '1' <= c && c <= '9' { // beginning of 1234.5
		 = state1
		return scanBeginLiteral
	}
	return (c, "looking for beginning of value")
}

There is such a piece of code that deals with the first character and finds that it isnThere is special processing and the next character processing function is set to stateN

// stateN is the state after reading `n`.
func stateN(s *scanner, c byte) int {
	if c == 'u' {
		 = stateNu
		return scanContinue
	}
	return (c, "in literal null (expecting 'u')")
}

That is, the next character must beu, the next character processing function is stateNu

// stateNu is the state after reading `nu`.
func stateNu(s *scanner, c byte) int {
	if c == 'l' {
		 = stateNul
		return scanContinue
	}
	return (c, "in literal null (expecting 'l')")
}

That is, the next character must bel, the next character processing function is stateNul

// stateNul is the state after reading `nul`.
func stateNul(s *scanner, c byte) int {
	if c == 'l' {
		 = stateEndValue
		return scanContinue
	}
	return (c, "in literal null (expecting 'l')")
}

That is, the next character must bel, and the next character processing function is stateEndValue.

visiblecheckValidFunctions have special handling of true, false, etc. Pay attention when using it.

For functions, it is found through debugging that it also has special processing for null pointers.

type ptrEncoder struct {
	elemEnc encoderFunc
}

func (pe ptrEncoder) encode(e *encodeState, v , opts encOpts) {
	if () {
		("null")
		return
	}
	if ++;  > startDetectingCyclesAfter {
		// We're a large number of nested  calls deep;
		// start checking if we've run into a pointer cycle.
		ptr := ()
		if _, ok := [ptr]; ok {
			(&UnsupportedValueError{v, ("encountered a cycle via %s", ())})
		}
		[ptr] = struct{}{}
		defer delete(, ptr)
	}
	(e, (), opts)
	--
}

If it is a null pointer, the string "null" will be returned and no error will be reported.

Int type will be parsed into float64

type Person struct {
	Name string
	Age  int
}

func main() {
	p := &Person{
		Name: "text",
		Age:  18,
	}

	bytes, err := (p)
	checkError(err)

	pMap := make(map[string]interface{})
	err = (bytes, &pMap)
	checkError(err)
	for k, v := range pMap {
		("k:%s,v:%+v, vtype:%v\n", k, v, (v))
	}
}

func checkError(err error) {
	if err != nil {
		("err:%+v\n", err)
	}
}

result

k:Name,v:text, vtype:string
k:Age,v:18, vtype:float64

Apparently, the Age type becomes float64. What problems will be caused? When the int size exceeds 6 digits, it becomes a scientific counting method. For example, Age=1234567, the result is

k:Name,v:text, vtype:string
k:Age,v:1.234567e+06, vtype:float64

At this time, if you directly update the map to db, the field that was originally of type int becomes float, and an error will be reported

This is the article about the pitfalls and solutions encountered by Golang parsing JSON. For more relevant content on Golang parsing JSON, please search for my previous articles or continue browsing the related articles below. I hope everyone will support me in the future!