Anconia
Anconia

Reputation: 4028

Parsing a JSON API in Node

I'm building a node wrapper for an external API and am having issues parsing the JSON response. The following code makes the request:

https = require "https"
querystring = require "querystring"

API_HOST = "api.lob.com"
API_PATH = "/v1"

startResponseHandler = (req, cb) ->
  if typeof cb isnt "function" then console.log "Error: callback needs to be a function!"
  req.on 'response', (res) ->
    response = ''
    res.setEncoding 'utf8'
    res.on 'data', (stream) ->
      response += stream
    res.on 'end', () ->
      error = null
      try
        response = JSON.parse response
        if res.statusCode != 200 or 201
          response = null
          error = new Error response.error.message
          error.name = response.error.type
          error.code = response.error.code
          error.param = response.error.param
      catch e
        error = new Error "Invalid JSON"
        response = null
      cb error, response
  req.on 'error', (error) ->
    cb error

module.exports = lob = (api_key) ->

  # This function makes the request
  makeRequest = (method, path, data, cb) ->
    data = querystring.stringify data
    options = 
      hostname: API_HOST
      path: "#{API_PATH}/#{path}"
      method: method
      auth: "#{api_key}:"
      headers:
        'Content-Type' : 'application/x-www-form-urlencoded'
        'Content-Length' : data.length
    req = https.request options
    startResponseHandler req, cb
    req.write data if method is "POST" or "PUT"
    req.end()

  # GET, POST, DELETE, PUT functions
  _get = (path, cb) ->
    makeRequest "GET", path, {}, cb

  _post = (path, data, cb) ->
    makeRequest "POST", path, data, cb

  _del = (path, cb) ->
    makeRequest "DELETE", path, {}, cb

  _put = (path, data, cb) ->
    makeRequest "PUT", path, data, cb

  # Jobs
  jobs:

    createJob: (data, cb) ->
      _post "/jobs/", data, cb

And the below code was written to test the wrapper:

api_key = "test_0dc8d51e0acffcb1880e0f19c79b2f5b0cc"

lob     = require('../src/lob')(api_key)
should  = require("should")
chai    = require("chai")

data = 
  name: "Michigan fan letter"
  to: "adr_43769b47aed248c2"
  from: "adr_7f9ece71fbca3796"
  object1: "obj_7ca5f80b42b6dfca"
  object2: "obj_12128d3aad2aa98f"

describe "Job", ->
  @timeout(10000)
  describe "create", ->
    it "should create a job with address_id", (done) ->
      lob.jobs.createJob data , (new_job) ->
        new_job['name'].should.equal(data['name'])
        done()

However, when I run the mocha test ($ mocha --compilers coffee:coffee-script) I receive the following error:

1) Job create should create a job with address_id:

      + expected - actual

      +"Michigan fan letter"
      -"Error"

EDIT

Below is the response from stream inside res.on 'data':

{
    "id": "job_7ecc50bea15178b8e07a",
    "name": "Michigan fan letter",
    "price": "1.26",
    "to": {
        "id": "adr_43769b47aed248c2",
        "name": "Harry Zhang",
        "email": "[email protected]",
        "phone": "5555555555",
        "address_line1": "123 Test Street",
        "address_line2": "Unit 199",
        "address_city": "Mountain View",
        "address_state": "CA",
        "address_zip": "94085",
        "address_country": "UNITED STATES",
        "date_created": "2013-07-20T05:53:25+00:00",
        "date_modified": "2013-07-20T05:53:25+00:00",
        "object": "address"
    },
    "from": {
        "id": "adr_7f9ece71fbca3796",
        "name": "Harry Zhang",
        "email": "[email protected]",
        "phone": "5555555555",
        "address_line1": "123 Test Avenue",
        "address_line2": "Unit 401",
        "address_city": "Seattle",
        "address_state": "WA",
        "address_zip": "98122",
        "address_country": "UNITED STATES",
        "date_created": "2013-07-20T05:55:19+00:00",
        "date_modified": "2013-07-20T05:55:19+00:00",
        "object": "address"
    },
    "status": "processed",
    "tracking": null,
    "packaging": {
        "id": "1",
        "name": "Smart Packaging",
        "description": "Automatically determined optimal packaging for safe and secure delivery",
        "object": "packaging"
    },
    "service": null,
    "objects": [
        {
            "id": "obj_7ca5f80b42b6dfca",
            "name": "Michigan is great",
            "quantity": "1",
            "full_bleed": "0",
            "double_sided": "0",
            "date_created": "2013-07-20T05:57:32+00:00",
            "date_modified": "2013-07-20T05:57:32+00:00",
            "setting": {
                "id": "101",
                "type": "Documents",
                "description": "Color Document",
                "paper": "20lb Paper Standard",
                "width": "8.500",
                "length": "11.000",
                "color": "Color",
                "notes": "50 cents per extra page",
                "object": "setting"
            },
            "url": "http://assets.lob.com/obj_7ca5f80b42b6dfca",
            "object": "object"
        },
        {
            "id": "obj_12128d3aad2aa98f",
            "name": "GO BLUE",
            "quantity": "1",
            "full_bleed": "0",
            "double_sided": "0",
            "date_created": "2013-07-31T00:58:35+00:00",
            "date_modified": "2013-07-31T00:58:35+00:00",
            "setting": {
                "id": "100",
                "type": "Documents",
                "description": "Black and White Document",
                "paper": "20lb Paper Standard",
                "width": "8.500",
                "length": "11.000",
                "color": "Black and White",
                "notes": "12 cents per extra page",
                "object": "setting"
            },
            "url": "http://assets.lob.com/obj_12128d3aad2aa98f",
            "object": "object"
        }
    ],
    "date_created": "2014-01-25T03:10:10+00:00",
    "date_modified": "2014-01-25T03:10:10+00:00",
    "object": "job"
}

Which exactly matches up to the example response in the documentation

{
    "id": "job_754d8b14dd31587d6873",
    "name": "Michigan fan letter",
    "price": "0.96",
    "to": {
        "id": "adr_43769b47aed248c2",
        "name": "Harry Zhang",
        "email": "[email protected]",
        "phone": "5555555555",
        "address_line1": "123 Test Street",
        "address_line2": "Unit 199",
        "address_city": "Mountain View",
        "address_state": "CA",
        "address_zip": "94085",
        "address_country": "UNITED STATES",
        "date_created": "2013-07-20T05:53:25+00:00",
        "date_modified": "2013-07-20T05:53:25+00:00",
        "object": "address"
    },
    "from": {
        "id": "adr_7f9ece71fbca3796",
        "name": "Harry Zhang",
        "email": "[email protected]",
        "phone": "5555555555",
        "address_line1": "123 Test Avenue",
        "address_line2": "Unit 401",
        "address_city": "Seattle",
        "address_state": "WA",
        "address_zip": "98122",
        "address_country": "UNITED STATES",
        "date_created": "2013-07-20T05:55:19+00:00",
        "date_modified": "2013-07-20T05:55:19+00:00",
        "object": "address"
    },
    "status": "processed",
    "tracking": null,
    "packaging": {
        "id": "1",
        "name": "Smart Packaging",
        "description": "Automatically determined optimal packaging for safe and secure delivery",
        "object": "packaging"
    },
    "service": null,
    "objects": [
        {
            "id": "obj_7ca5f80b42b6dfca",
            "name": "Michigan is great",
            "quantity": "1",
            "full_bleed": "0",
            "double_sided": "0",
            "date_created": "2013-07-20T05:57:32+00:00",
            "date_modified": "2013-07-20T05:57:32+00:00",
            "setting": {
                "id": "101",
                "type": "Documents",
                "description": "Color Document",
                "paper": "20lb Paper Standard",
                "width": "8.500",
                "length": "11.000",
                "color": "Color",
                "notes": "50 cents per extra page",
                "object": "setting"
            },
            "object": "object"
        }
    ],
    "date_created": "2014-01-18T19:52:27+00:00",
    "date_modified": "2014-01-18T19:52:27+00:00",
    "object": "job"
}

Here is the data from JSON.parse response

{ id: 'job_9973e060bd8147f97f5f',
  name: 'Michigan fan letter',
  price: '1.26',
  to:
   { id: 'adr_43769b47aed248c2',
     name: 'Harry Zhang',
     email: '[email protected]',
     phone: '5555555555',
     address_line1: '123 Test Street',
     address_line2: 'Unit 199',
     address_city: 'Mountain View',
     address_state: 'CA',
     address_zip: '94085',
     address_country: 'UNITED STATES',
     date_created: '2013-07-20T05:53:25+00:00',
     date_modified: '2013-07-20T05:53:25+00:00',
     object: 'address' },
  from:
   { id: 'adr_7f9ece71fbca3796',
     name: 'Harry Zhang',
     email: '[email protected]',
     phone: '5555555555',
     address_line1: '123 Test Avenue',
     address_line2: 'Unit 401',
     address_city: 'Seattle',
     address_state: 'WA',
     address_zip: '98122',
     address_country: 'UNITED STATES',
     date_created: '2013-07-20T05:55:19+00:00',
     date_modified: '2013-07-20T05:55:19+00:00',
     object: 'address' },
  status: 'processed',
  tracking: null,
  packaging:
   { id: '1',
     name: 'Smart Packaging',
     description: 'Automatically determined optimal packaging for safe and secure delivery',
     object: 'packaging' },
  service: null,
  objects:
   [ { id: 'obj_7ca5f80b42b6dfca',
       name: 'Michigan is great',
       quantity: '1',
       full_bleed: '0',
       double_sided: '0',
       date_created: '2013-07-20T05:57:32+00:00',
       date_modified: '2013-07-20T05:57:32+00:00',
       setting: [Object],
       url: 'http://assets.lob.com/obj_7ca5f80b42b6dfca',
       object: 'object' },
     { id: 'obj_12128d3aad2aa98f',
       name: 'GO BLUE',
       quantity: '1',
       full_bleed: '0',
       double_sided: '0',
       date_created: '2013-07-31T00:58:35+00:00',
       date_modified: '2013-07-31T00:58:35+00:00',
       setting: [Object],
       url: 'http://assets.lob.com/obj_12128d3aad2aa98f',
       object: 'object' } ],
  date_created: '2014-01-25T23:12:37+00:00',
  date_modified: '2014-01-25T23:12:37+00:00',
  object: 'job' }

Upvotes: 1

Views: 561

Answers (1)

Louis
Louis

Reputation: 151441

I see three problems in that code. I don't usually use CoffeeScript. So if I'm misreading please correct me:

  1. The callback is called like this:

    cb error, response
    

    First argument is an error object and the second argument is the response, however in the test the callback is like this:

    lob.jobs.createJob data , (new_job) ->
             new_job['name'].should.equal(data['name'])
    

    So new_job is the error object. This does not explain everything, however, because you are getting an error. But even if there were no error, the value of new_job would not be the response.

    The reason the test shows that the string Error is the actual value is that Error objects have a name field which is set to the name of the class of the exception. (So new Error().name evaluates to "Error".)

  2. This section of code also looks incorrect:

    try
      response = JSON.parse response
      if res.statusCode != 200 or 201
        response = null
        error = new Error response.error.message
        error.name = response.error.type
        error.code = response.error.code
        error.param = response.error.param
    catch e
      error = new Error "Invalid JSON"
      response = null
    cb error, response
    

    In the if branch the response is set to null and then some fields of response are accessed. This will cause an exception. And this exception will then be interpreted as bad JSON due to the way the try... catch clause is set. The try... catch clause should be narrowed to cover only the JSON.parse call:

    try
      response = JSON.parse response
    catch e
      error = new Error "Invalid JSON"
      response = null
    
  3. This test is incorrect:

      if res.statusCode != 200 or 201
    

    It becomes JavaScript:

      if (res.statusCode !== 200 || 201)
    

    The part after the || makes it always true. Something like the following CoffeeScript code seems to be what is intended:

      if res.statusCode not in [200, 201]
    

So it does not matter if the request is successful or not. Because of the third problem, theif test will always be true, the branch always will be taken and the second problem will always occur. So the Mocha test will always fail.

Upvotes: 2

Related Questions