JSON key ordering

I’m replacing a PHP process at work that updates Solr indexes. These index names are dynamically created from different attributes, so at the end, I had to use a map[string]interface{} to build these values.

The issue comes at the PHP side of the process (I know this isn’t a PHP forum, but please bear with me) as the consumer is coded in the following way:

// for loop here with $item declared
    $key = array_shift($item)
// end for loop

So, as you can see, this code is expecting the first value in the document to be a specific value. And that is because the process that builds and send data to Solr in PHP prepends that value in the array to be encoded as JSON as the first key/value pair and Solr respects that ordering when storing the documents, and, somehow, PHP encodes that object into JSON respecting the order of the keys, that’s why these guys are assuming on the other end that the value will be there.

According to the JSON RFC specification there’s no restriction on the order of the keys, which is why I assume json.Marshal([]byte, interface{}) doesn’t care about the order of the keys and you always get a different ordering.

Has anyone faced a situation where the order of the keys in a JSON object is important and, if so, how can you ensure key ordering?

I know that this can be achieved using a struct, but as I was saying, these attributes can change over time and it would be pretty difficult to maintain hundreds of structs with different structures.

Thanks in advance and I hope have made myself clear!

According to the JSON RFC spec, the only object that is ordered by definition is an array.

So if sender and receiver have to agree on a specific order of elements, I see only two options:

  1. Use sortable values as keys and have the receiver sort the key/value pairs before iterating over the keys.
  2. Transform the data into an array. E.g. first entry = key, second entry = value, third entry = key, and so on.
1 Like

That said, the Go JSON encoder orders map keys alphabetically when marshaling. Are you sure you see random key order?

Sorry guys for the delay and thank you for your answers.

I think I didn’t formulate the question correctly. The whole issue is keeping the desired ordering when marshaling an object to JSON:

package main

import (
	"fmt"
	"encoding/json"
)

func main() {
	// Clipped document
	document := map[string]interface{}{
        "facet_is_special_price":[]string{"1"},
        "score_outlet":"0", 
        "skus":[]string{"DI139BE71WDWDFMX", "DI139BE71WDWDFMX-519406"}, 
        "facet_novelty_two_days":[]string{"0"},
        "facet_brand":[]string{"139"},
        "sku":[]string{"DI139BE71WDWDFMX"},
    }
	// The order here appears to be random everytime, which is OK
	fmt.Printf("Document (random order)\n%#v\n", document)
	encoded, err := json.Marshal(&document)
	if err != nil {
		panic(err)
	}
	// Right, marshaling the object into JSON orders the keys alphabetically
	fmt.Printf("JSON (alphatical order)\n%s", encoded)
	// There's a special attribute on the PHP side is expecting to be the first key in the object (array)
	if sku, found := document["sku"]; found {
		newDocument := make(map[string]interface{}, len(document))
		newDocument["sku"] = sku
		delete(document, "sku")
		for key, value := range document {
			newDocument[key] = value
		}
		encoded, err := json.Marshal(&newDocument)
		if err != nil {
			panic(err)
		}
		// Still the the keys are order alphabetically, which I understand is the expected behaviour
		fmt.Printf("JSON\n%s", encoded)
	}
}

Don’t get me wrong, I know the PHP code is not what it should be, it will fail when the expected value is not at the first place or when it isn’t there at all. I just wanted to know if there’s a way to keep the ordering or the position of the keys when converting the object into JSON so it is not needed to touch the legacy PHP code (even though it isn’t optimal).

My goal is to achieve the following:

// Given:
document := make(map[string]interface{})
document["sku"] = "ABC123"
// ... 
document["a_last_attribute"] = "some value"
// and when
encoded, _ := json.Marshal(&document)
// get the following: {"sku": "ABC123", "a_last_attribute"}

Again, thanks for your help and have a good one.

You cannot, using Go a map, as it does not have ordered keys. You would need to create your own ordered map type, internally being something like a list of key/value pairs, and implement JSON marshalling/unmarshalling for it.

This looks like it may be something that already works for you: https://github.com/iancoleman/orderedmap

1 Like

I see.

I’ll take a look at this package. Thank you so much @calmh, have a good day.

1 Like

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.