Mongodb import object with numbers as keys results in array

I have a simple .json which I am trying to import:

{
  "data": {
    "plans": {
      "1": "14",
      "2": "20",
      "3": "40"
    }
  }
}

When I use MongoDB Compass to directly import the json file, the plans object is converted into an array:

{ "_id": { "$oid": "5fe3ff5d909016064978f2bd" }, "plans": [null, "14", "20", "40"] }

Am I doing something wrong? Or can I not use numbers as keys in JSON

I think it is doing what you told it to do :slight_smile:
What is an object with numbered indices other than, precisely, an array?
So Compass inserts your JSON as an array with a null value for the 0th offset.

1 Like

I never thought of it like that. But what if my object isn’t in sequence?

{
  "data": {
    "plans": {
      "1": "14",
      "2": "20",
      "3": "40",
     "5": "50"
    }
  }
}

You would end up with:

plans : [ null , 14 , 20 , 40 , null , 50 ]

Note that plans.X would gives the same value whether plans is an array or an object. Array are simply, more efficient as no storage is use for the key (index). Except may be for sparse array.

Also note that with mongo (the old shell, I do not know about mongosh), you do not end up with an array.

> db.test.insertOne( { _id:1 , plans : { "1": "one" , "3" : "three" } } )
{ "acknowledged" : true, "insertedId" : 1 }
> db.test.find()
{ "_id" : 1, "plans" : { "1" : "one", "3" : "three" } }
2 Likes

Hello, @steevej and @Jack_Woehr, I am not sure why its working properly through mongoimport command and why its not through mongo compass,

Import through mongoimport: (Working)

> mongoimport --db dbName --collection collectionName <fileName.json  

This results exact input result:

{
  "_id": { "$oid": "5fe3ff5d909016064978f2bd" },
  "data": {
    "plans": {
      "1": "14",
      "2": "20",
      "3": "40"
    }
  }
}

Import from mongo compass: (Not Working)

This results:

{ 
  "_id": { "$oid": "5fe3ff5d909016064978f2bd" }, 
  "plans": [null, "14", "20", "40"] 
}

Ultimately both are same JSON import and using same JSON file, but results are different why it is happening?

1 Like

Every time one adds a tool layer on top of an existing layer one adds complexity and something new to debug.
If I were troubled by the phenomenon @turivishal is describing here is the way I would explore this:

  1. Perform this insert in node.js
  2. Perform this insert in my favorite programming language
  3. Perform this insert in mongo shell
  4. Perform this insert in mongosh shell
  5. Import this data via mongoimport
  6. Import this data via compass

In fact, you have mostly already done all of this.
The answers you get are what the truth is!
If Compass does something different, the answer is in the Compass source, which is publicly available.

2 Likes

@Jack_Woehr Now that we’ve all acknowledged that this is in fact a bug and not correct syntax, Isn’t there a process for bugs to be submitted within the mongodb community?

https://jira.mongodb.org/plugins/servlet/samlsso

The issue has been submitted https://jira.mongodb.org/browse/COMPASS-4548

It will take some days to process.

2 Likes

Hi @promisetech,

There is indeed a standard process to Submit a Bug Report for MongoDB Compass. The short scoop is that bug reports should be submitted to the COMPASS project in MongoDB’s JIRA issue tracker. The Bug Report documentation link includes a screenshot with some further details, as well as the procedure for submitting Feature Requests.

Thanks @turivishal for being proactive and submitting (and sharing) a bug report.

Regards,
Stennie

1 Like

Hi @promisetech,

JSON requires key names to be strings, so using numeric strings is technically valid. However, JavaScript has some interesting assumptions (and unexpected coercion) for numeric values so I’d recommend against using numeric strings as keys.

Here’s an example of JavaScript producing unexpected results for what appear to be similar objects.

a) Doc with string key names that could be interpreted as numbers:

doc = {
  "data": {
    "plans": {
      "2": "20",
      "1": "14",
      "3": "40"
    }
  }
}
Output in a `mongo` or `node` shell (numeric strings)
{
  "data": {
    "plans": {
      "1": "14",
      "2": "20",
      "3": "40"
    }
  }
}

==> JavaScript sorts the object keys

b) Doc with string key names that are alphanumeric:

doc = {
  "data": {
    "plans": {
      "v2": "20",
      "v1": "14",
      "v3": "40"
    }
  }
}
Output in a `mongo` or `node` shell (alphanumeric strings)
{
  "data": {
    "plans": {
      "v2": "20",
      "v1": "14",
      "v3": "40"
    }
  }
}

==> Object keys are maintained in insertion order!

As my example above demonstrates, the order of keys in a generic JavaScript Object is not defined (or guaranteed to be preserved). In particular, there is some legacy handling for key names that can be parsed as a 32-bit integer: 164 - v8 - V8 JavaScript Engine - Monorail. JavaScript does have order-preserving types like Map objects and arrays, but the default Object behaviour is almost what you expect (except when it isn’t).

Since Compass is a JavaScript application, it inherits some of these legacy quirks that have to be coded around.

The mongoimport tool is written in Go, so its JSON implementation does not have to deal with the added quirks of JavaScript handling.

Regards,
Stennie

2 Likes

I also encountered the same problem I took a backup of a collection in JSON using compass and after some modifications I had to imported the same JSON file.

Each document of my collection has a responses object which is parsed as an array after import.

Original object

responses = {
     "101":"text",
    "201":'text
}

after import

responses = [null, null , ......."text", null, null, ........... "text"]

I wrote this script. This helped me out cleaning multiple collections.

db = pymongo.MongoClient(MONGODB_URI).texam
f = db.responses.find()
for i in f: 
    temp = {}
    flag = False
    for n, j in enumerate(i['responses']): 
        if j != None:
            temp[str(n)] = j
        else: 
            flag = True
    print(i)
    if flag:
        db.responses.update_one(i, {"$set":{'responses':temp}})

I had to use flag as some documents were right.