Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

MongoError: key $ must not start with '$' when store JSON object generated by xml2js module

Everyone,

MongoDB db.version() is 3.0.5
mongodb package.json shows 2.0.42 version
xml2js package.json shows 0.4.9 version  

I have already googled the error, and read through all the existing questions and none seem to match my circumstances (neither the jira issue, nor the google group discussions, etc). This could be related to mongodb native driver (or MongoDB server version), xml2js or something else.

I have done lots of testing and did found a way round the problem but, I am curious to know what is the issue.

I have an app that does the following:

  1. Upload XForm (survey.xml) to the ExpressJS
  2. Use the xml2js library to convert the XML to JSON object (sample shown below)
  3. Iterate through JSON object created in 2 and remove unnecessary fields (snippet code shown below)
  4. Push the modified JSON object in 3 to MongoDB using mongodb native driver

Step 4 fails with error (shown in title of the question).

  1. There are 2 JSON objects, one is the survey form, containing $ keys but, before I insert the document into mongodb, I remove the $ keys using the following code

    (function traverse(o) {
    for (var i in o) {
      if (o[i] !== null && typeof(o[i])=="object") {
          //going on step down in the object tree!!
        if(o[i].$) {
          var ref = "";
          if(o[i].$.ref) {
            ref = o[i].$.ref;
          } else if (o[i].$.nodeset) {
            ref = o[i].$.nodeset;
          }
          o[i].ref = ref;
          o[i].$ = undefined;
          var chunks = ref.split('/');
          o[i]['name'] = chunks[chunks.length - 1];
        }
        traverse(o[i]);
      }
    }
    })(body);
    
  2. I console log the output of the above before adding it to MongoDB and the console.log shows no sign of keys with $.

  3. Assuming that step 2 actually fails due to a $ in the key, which is not true, I have another JSON which is the response of the survey and contains no $ whatsoever, but that also fails with the same error (MongoError: key $ must not start with '$').

    db.collection('submissions').insert(jsonObject, function(err, result) {
      if(err) console.log('error is : ' + err);
      console.log('insertion result : ' + JSON.stringify(result));
    });
    

Below is the XML form submission

   <?xml version='1.0' ?>
   <ppe id="ppe_checklist_new">
    <starting_repeat>
        <location>dark_room</location>
        <ppe_dark>safety_glasses</ppe_dark>
        <xyz_group>
            <condition>condition_missing</condition>
            <test_condition>b</test_condition>
        </xyz_group>
    </starting_repeat>
    <starting_repeat>
        <location>nitrogen_store</location>
        <ppe_nitrogen>leather_gloves_s</ppe_nitrogen>
        <xyz_group>
            <condition>condition_replacing</condition>
            <test_condition />
        </xyz_group>
    </starting_repeat>
    <starting_repeat>
        <location>nitrogen_store</location>
        <ppe_nitrogen>blue_gloves_m</ppe_nitrogen>
        <xyz_group>
            <condition />
            <test_condition>b</test_condition>
        </xyz_group>
    </starting_repeat>
    <starting_repeat>
        <location>cold_room_first</location>
        <ppe_cold>hearing_muff_1</ppe_cold>
        <xyz_group>
            <condition>condition_ok</condition>
            <test_condition>f</test_condition>
        </xyz_group>
    </starting_repeat>
    <sample_group>
        <date>2015-08-24</date>
        <random_number>55</random_number>
    </sample_group>
    <another_group>
        <another_repeat>
            <sample_text>Sample text 1</sample_text>
            <image />
        </another_repeat>
        <another_repeat>
            <sample_text>Sample text 2</sample_text>
            <image />
        </another_repeat>
    </another_group>
    <form_done>OK</form_done>
    <survey_start>2015-08-24T16:55:23.185+01</survey_start>
    <survey_end>2015-08-24T16:57:24.460+01</survey_end>
    <survey_day>2015-08-24</survey_day>
    <survey_device>353490061313389</survey_device>
    <meta>
        <instanceID>uuid:2aba0eff-5350-47e3-9e9c-9606d2c9e7d6</instanceID>
    </meta>
</ppe>

I feed the above to xml2js module with following configs:

function parseXMLToJS(filename, path, callback) {
  var parser = new xml2js.Parser({explicitArray:false});
  var fileURL = path + filename;

  var data = fs.readFile(fileURL, function(err, data) {
    if(err) {
      logger.error('Error reading submission file. Error %', err);
    } else {
      parser.parseString(data, function (err, result) {
        if(err) {
          logger.error('Error parsing XML to JS. Error : %',  err);
          callback({parsed:false, result:result});
        } else {
          callback({parsed:true, result:result});
        }
      });
    }
  });
}

and the module generates a JSON object as shown below:

{
    "ppe": {
        "starting_repeat": [
            {
                "location": "dark_room",
                "ppe_dark": "safety_glasses",
                "xyz_group": {
                    "condition": "condition_missing",
                    "test_condition": "b"
                }
            },
            {
                "location": "nitrogen_store",
                "ppe_nitrogen": "leather_gloves_s",
                "xyz_group": {
                    "condition": "condition_replacing",
                    "test_condition": ""
                }
            },
            {
                "location": "nitrogen_store",
                "ppe_nitrogen": "blue_gloves_m",
                "xyz_group": {
                    "condition": "",
                    "test_condition": "b"
                }
            },
            {
                "location": "cold_room_first",
                "ppe_cold": "hearing_muff_1",
                "xyz_group": {
                    "condition": "condition_ok",
                    "test_condition": "f"
                }
            }
        ],
        "sample_group": {
            "date": "2015-08-24",
            "random_number": "55"
        },
        "another_group": {
            "another_repeat": [
                {
                    "sample_text": "Sample text 1",
                    "image": ""
                },
                {
                    "sample_text": "Sample text 2",
                    "image": ""
                }
            ]
        },
        "form_done": "OK",
        "survey_start": "2015-08-24T16:55:23.185+01",
        "survey_end": "2015-08-24T16:57:24.460+01",
        "survey_day": "2015-08-24",
        "survey_device": "353490061313389",
        "id": "ppe_checklist_new",
        "uuid": "2aba0eff-5350-47e3-9e9c-9606d2c9e7d6"
    }
}

I tried checking whether the data I am inserting to MongoDB is object or not and turns out that it is.

typeof(result.result)
  1. Tried inserting the generated JSON object from xml2js via the Mongo Shell and it worked.
  2. Tried creating an JS object in my javascript code with the same content as JSON generated by xml2js and inserting that to MongoDB via mongodb native driver worked (even though I compared the two JSON and they were exactly the same).

In conclusion, I could say that mongodb native driver does not like the JSON object that is generated by xml2js module, why I do not know? I can't wait to know. I tried the following to work around

  1. Convert the XML 2 JSON using xml2js
  2. Stringify the returned JSON object from xml2js (x = JSON.stringify(obj);)
  3. Parsed the stringified JSON object back (parsedX = JSON.parse(x);)
  4. Inserted the parsed value into MongoDB via mongodb native drive, and it worked.

Regardless of what the issue, the above error message is kinda misleading or maybe not appropriate for this issue that is causing it. I could not find a way to get more detailed error (maybe there is a way that mongodb provide more explanation, would love to know).

Thank you for your time and patience.

like image 984
Raf Avatar asked Oct 30 '22 20:10

Raf


1 Answers

The following line of the traverse function just sets the $ field to a value of undefined without actually removing it.

o[i].$ = undefined;

Change that line to use delete to remove it instead:

delete o[i].$;

Your workaround using JSON.stringify and JSON.parse works because the undefined value can't be represented in JSON so the JSON.stringify call removes fields with that value.

like image 159
JohnnyHK Avatar answered Nov 15 '22 06:11

JohnnyHK