eBay Finding API - Why are findItemsAdvanced JSON result elements all arrays? - javascript

When calling findItemsAdvanced with RESPONSE-DATA-FORMAT=XML, the results are as expected, e.g:
<findItemsAdvancedResponse xmlns="http://www.ebay.com/marketplace/search/v1/services">
<ack>Success</ack>
<version>1.13.0</version>
<timestamp>2014-11-16T20:59:57.588Z</timestamp>
<searchResult count="0"/>
<paginationOutput>
<pageNumber>0</pageNumber>
<entriesPerPage>100</entriesPerPage>
<totalPages>0</totalPages>
<totalEntries>0</totalEntries>
</paginationOutput>
<itemSearchURL>http://www.ebay.co.uk/sch/i.html?_nkw=mytest1</itemSearchURL>
</findItemsAdvancedResponse>
But calling the same with RESPONSE-DATA-FORMAT=JSON, individual elements are all wrapped in []:
{"findItemsAdvancedResponse":[
{"ack":["Success"],
"version":["1.13.0"],
"timestamp":["2014-11-16T20:58:14.639Z"],
"searchResult":[
{"#count":"0"}],
"paginationOutput":[
{"pageNumber":["0"],
"entriesPerPage":["100"],
"totalPages":["0"],
"totalEntries":["0"]}],
"itemSearchURL":["http:\/\/www.ebay.co.uk\/sch\/i.html?&_nkw=mytest1"]
}]
}
This seems to make it a pain to extract results using Javascript e.g:
response.findItemsAdvancedResponse[0].paginationOutput[0].pageNumber[0]
Am I doing missing something here or doing something wrong? (If not will consider requesting the results in XML and using an XML=>JSON conversion tool...)

Seems no-one else is bothered by this?
I'm using AngularJS and I want to have the Ebay API as a backend. I have to write a $resource interceptor to collapse all arrays that only have 1 child to remove the redundant [] brackets.
A generic interceptor could solve this elegantly however I do think this is a bug.
I've asked the question on the Ebay Developer Form here: https://forums.developer.ebay.com/questions/16385/why-does-search-return-json-items-as-array.html#answer-16386
I have referenced this page on the Ebay forum - hope that helps others.
Edit:
... and for completeness, here is the code I used. It might not be pretty, but it worked for me. YMMV
var resp = {"findItemsAdvancedResponse":[
{"ack":["Success"],
"version":["1.13.0"],
"timestamp":["2014-11-16T20:58:14.639Z"],
"searchResult":[
{"#count":"0"}],
"paginationOutput":[
{"pageNumber":["0"],
"entriesPerPage":["100"],
"totalPages":["0"],
"totalEntries":["0"]}],
"itemSearchURL":["http:\/\/www.ebay.co.uk\/sch\/i.html?&_nkw=mytest1"]
}]
};
var flatten = function( obj ) {
var ret = {};
if( String === obj.constructor || Number === obj.constructor ) {
return obj;
}
for(var prop in obj) {
if(!obj.hasOwnProperty(prop)) continue;
if( String === obj[prop].constructor || Number === obj[prop].constructor ) {
ret[prop] = obj[prop];
continue;
}
if( Object.prototype.toString.call( obj[prop] ) === '[object Array]' ) {
if( obj[prop].length==0 )
ret[prop] = null;
if( obj[prop].length==1 && "0" in obj[prop] ) {
ret[prop] = flatten(obj[prop][0]);
} else {
ret[prop] = flatten(obj[prop]);
}
continue; // skip below: all arrays are Objects...!
}
if( Object === obj[prop].constructor ) {
ret[prop] = flatten( obj[prop] );
continue;
}
}
return ret;
};
console.log( resp );
resp = flatten( resp );
console.log( resp );
console.log( resp.findItemsAdvancedResponse.ack );

This is very similar to DerekC's parse function just quite a bit faster. It is geared towards simply finding elements that are arrays with Array.isArray() and a length of 1 with values that are not objects. Nothing fancy, super clean and super fast.
The function is called ebayFormat, just below the same response object that DerekC used.
var resp = {"findItemsAdvancedResponse":[
{"ack":["Success"],
"version":["1.13.0"],
"timestamp":["2014-11-16T20:58:14.639Z"],
"searchResult":[
{"#count":"0"}],
"paginationOutput":[
{"pageNumber":["0"],
"entriesPerPage":["100"],
"totalPages":["0"],
"totalEntries":["0"]}],
"itemSearchURL":["http:\/\/www.ebay.co.uk\/sch\/i.html?&_nkw=mytest1"]
}]
};
function ebayFormat(item) {
if (typeof item === 'object') {
if (Array.isArray(item) && item.length === 1 && typeof item[0] !== 'object') item = item[0];
else {
var keys = Object.keys(item),
i = 0,
len = keys.length;
for (; i < len; i++) {
if (typeof item[keys[i]] === 'object') item[keys[i]] = ebayFormat(item[keys[i]]);
}
}
}
return item;
}
console.log( resp );
resp = ebayFormat( resp );
console.log( resp );
console.log( resp.findItemsAdvancedResponse.ack );

I got the same problem, I made a recursive function to solve it (remove array when array length=1 and is not exclude)
objJsonFromEbay = findAndCorrect(objJsonFromEbay);
function findAndCorrect(obj){
let o = obj,
exclu = ['item'];
for (const key in o) {
if (o.hasOwnProperty(key)) {
const val = o[key];
if(Array.isArray(val) && val.length == 1){
if(exclu.indexOf(key) == -1)
o[key] = val[0];
o[key] = findAndCorrect(o[key]);
}else if(!Array.isArray(val) && typeof val == 'object'){
o[key] = findAndCorrect(val);
}
}
}
return o;
}
exclu is a Array for each element that you expect keep in a Array format.
Could be useful if ever you use it to get products or another data that you expect in a Array

I had the same issue, and was curious if I'd still have issues if I chose to use an XML response versus a JSON response, and use an XML-to-JSON converter on the returned data -- lo and behold, still the exact same issue.
The converter I would end up deciding to use is called xml2js, which seems to be extremely popular on NPM (north of 3.5M monthly downloads according to NPM at the time of this answer).
xml2js has an option called explicitArray within its parseString behavior, which behaves as they document:
Always put child nodes in an array if true; otherwise an array is created only if there is more than one.
This behavior seems to emulate some of the answers listed here, but because it's a part of a widely-used community solution, I feel more comfortable using it versus a home-grown solution.
In practice, this looks as simple as:
import { parseString as xml2js } from 'xml2js';
...
xml2js(someXmlString, { explicitArray: false }, (err, results) => {
// do something with `results`
});
https://www.npmjs.com/package/xml2js

This is JSON. What did you expect JSON to look like? :-)
{"findItemsAdvancedResponse":[
{"ack":["Success"],
"version":["1.13.0"],
"timestamp":["2014-11-16T20:58:14.639Z"],
"searchResult":[
{"#count":"0"}],
"paginationOutput":[
{"pageNumber":["0"],
"entriesPerPage":["100"],
"totalPages":["0"],
"totalEntries":["0"]}],
"itemSearchURL":["http:\/\/www.ebay.co.uk\/sch\/i.html?&_nkw=mytest1"]
}]
}
Try going to http://jsonviewer.stack.hu/ and pasting the JSON string into the "Text"-section and clicking the "Viewer" tab for a visual representation of the JSON data.
You may want to visit the Wikipedia article on JSON (JavaScript Object Notation):
http://en.wikipedia.org/wiki/JSON#Data_types.2C_syntax_and_example

Related

How do I handle indexOf returning 'null' without using try/catch(err)?

I'm populating a table with data - using fixed-data-table, which is a React.js component. However, that isn't so important at this stage.
The table has a search box where the issue stems from.
First, here's the interesting part of the code.
for (var index = 0; index < size; index++) {
if (!filterBy || filterBy == undefined) {
filteredIndexes.push(index);
}
else {
var backendInfo = this._dataList[index];
var userListMap = hostInfo.userList;
var userListArr = Object.values(userListMap);
function checkUsers(){
for (var key in userListArr) {
if (userListArr.hasOwnProperty(key) && userListArr[key].text.toLowerCase().indexOf(filterBy) !== -1) {
return true;
}
}
return false;
}
if (backendInfo.firstName.indexOf(filterBy) !== -1 || backendInfo.lastName.toLowerCase().indexOf(filterBy) !== -1 || backendInfo.countryOrigin.toLowerCase().indexOf(filterBy) !== -1
|| backendInfo.userListMap.indexOf(filterBy) !== -1) {
filteredIndexes.push(index);
}
}
}
This is rendered and the last part is throwing errors if you input something in the table, and a column returns null from the user input.
The thing is, I can make the code work if I change the last part to ..
try {
if (backendInfo.firstName.indexOf(filterBy) !== -1 || backendInfo.lastName.toLowerCase().indexOf(filterBy) !== -1 || backendInfo.countryOrigin.toLowerCase().indexOf(filterBy) !== -1
|| backendInfo.userListMap.indexOf(filterBy) !== -1) {
filteredIndexes.push(index);
}
}
catch(err) {
console.log('Exception')
}
With the try/catch, it works 100% as intended and handles the indexOf returning null... But this can't be the way to properly handle it - I'm assuming this sort of exception handling is, well, supposed to be for rare exceptions, and shouldn't really be used on the front-end as much as the backend.
How do I handle indexOf returning null in the above Javascript code? It might return null in any of the sources columns that are being populated.
If a key cannot be found, JS will throw an error. Try-catch is a good way to fix these errors, but there is an alternative:
You could check if keys exist in an object prior to pushing a value into it.
var data = { };
var key = "test";
// your method works great
try {
var value = data.firstname.indexOf(key);
} catch (err) {}
// another method, I'd prefer the try/catch
var value = data.firstname ? data.firstname.indexOf(key) : undefined;
// test if the object is the type of object you are looking for
// this is in my opinion the best option.
if(data.firstname instanceof Array){
var value = data.firstname.indexOf(key);
}
// you can use the last option in your code like this:
var firstnameHasKey = data.firstname instanceof Array && ~data.firstname.indexOf(key);
var lastnameHasKey = data.lastname instanceof Array && ~data.lastname.indexOf(key);
if(firstnameHasKey || lastnameHasKey){
// logics
}
If you test the instanceof && indexOf, there will never be an error. If firstname is undefined, the indexOf will never be checked.
Ofcourse you can use this for other types:
var myDate = new Date();
myDate instanceof Date; // returns true
myDate instanceof Object; // returns true
myDate instanceof String; // returns false
MDN documentation

Check exactly one boolean option set

Well, this is kind of hacky:
function b2n(boo) {
return boo ? 1 : 0;
}
if(b2n(opt1) + b2n(opt2) + b2n(opt3) !== 1) {
throw new Error("Exactly one option must be set");
}
Is there a better way to do this in Javascript? Using any of
more intelligent boolean/number handling
sneaky array or functional operations
And so forth. Javascript and Node solutions welcome.
In my actual problem, the options are coming from the Node module commander, so I'm not dealing with true boolean, just truthy and falsy things. There may be a commander-solution too.
Assuming you had an array of options, you could do:
if(opts.filter(Boolean).length !== 1) {}
It seems to me though that you ought to have one variable with three possible states instead...
var opt = 'a'; // (or 'b', or 'c')
You can do this :
if ( !!opt1 + !!opt2 + !!opt3 !== 1 ) {
It works because
!! makes a boolean from any value (true if the objects evaluates as true in if(value))
when adding booleans you get 1 for true and 0 for false.
You mentioned in your comment that this is coming from a commander options object.
You can do this more elegantly using Lodash:
if (_(options).values().compact().size() === 1)
If you only want to count a subset of the options, you can insert
.pick('a', 'b', 'c')
if ([opt1, opt2, opt3].reduce(function(x, y) { return x + !!y }, 0) == 1) {
// exactly one
};
ECMAScript 5 reduce function.
I think you are being too clever, what's wrong with:
var optionsSelected = 0;
if( opt1 ) optionsSelected++;
if( opt2 ) optionsSelected++;
if( opt3 ) optionsSelected++;
if( optionsSelected !== 1 ) {
throw new Error("Exactly one option must be set");
}
Of course I can play the clever game too:
if( opts.filter(Boolean).length !== 1 ) {
throw new Error("Exactly one option must be set");
}
#spudly is on the right track, but it could be a little more compact:
if( [opt1,opt2,opt3].filter(function(x){return x}).length!==1 ) {
throw new Error("Exactly one option must be set");
}
See ES5's filter method for more information.

Check JSON data structure for existance of lower level names

Can I use 'in' to check existence of non top level names in a JSON data structure in a single comparison?
I have n tier JSON data structures,
I can do: if("mbled" in jsonData), works fine
For a lower tier name:
I can do this but (works but gets clunky as I go deeper): if("pnpenvsense1" in jsonData && "light" in jsonData.pnpenvsense1)
I'd prefer something like (doesn't work, always returns false): if("pnpenvsense1.light" in jsonData)
something like:
function objExists(path, struct){
path = path.split('.');
for(var i=0, l=path.length; i<l; i++){
if(!struct.hasOwnProperty(path[i])){ return false; }
struct = struct[path[i]];
}
return true;
}
objExists('pnpenvsense1.light', jsonData);
try
// will check if it is defined and not false
if(pnpenvsense1.light !== undefined && pnpenvsense1.light != false )
{ // my code }
http://jsfiddle.net/
arr = new Array();
arr['myKey'] = 12;
arr['myOtherKey'] = { "first" : "yes" , "second" : false };
if(arr.myKey !== undefined && arr.myKey != false)
alert("1 -> " + arr.myKey);
if(arr.myOtherKey.first !== undefined && arr.myOtherKey.first != false)
alert("2 -> " + arr.myOtherKey.first);

Explode a string into a hashmap for searching?

I have a string like this being returned from my backend:
"1,2,3,4,5,6"
I have a large array locally and want to display only those items not in this list, so I was thinking of exploding this string into an array but how can I search efficiently? As far as I know there are no hashmaps in JS so how does one do this? I just need to check for key existence.
All Javascript objects are also hash tables that can store string or numeric keys:
var x = {};
x["foo"] = 1;
if("foo" in x) { alert("hello!"); }
if("bar" in x) { alert("should never see this"); }
"1,2,3,4,5,6".split(",").some(function(letter) {
return letter === '2'
});
Warning: Might not work in IE (or other crappy browser)
Cross browser version (that relies on native code for performance):
var arr = "1,2,3,4,5,6".split(",");
if(arr.some)
{
arr.some(function(letter) {
return letter === '2'
});
}
else
{
for(var i = 0 ; i < arr.length ; i++ )
{
if(arr[i] === '2') return true;
}
}

Easy way to evaluate path-like expressions in Javascript?

If I have a JavaScript object such as:
var x = {foo: 42, bar: {fubar: true}}
then I can get the value true with var flag = x.bar.fubar. I'd like to be able to separate out and store the path "bar.fubar", then evaluate it dynamically. Something like:
var paths = ["bar.fubar", ...];
...
var flag = evalPath( x, paths[0] );
Obviously I could write a simple parser and evaluator for a basic path expression grammar. But under DRY principles I wonder if there's already an existing way to do something like evalPath built-in to JavaScript, or a small library that would do the job? I also anticipate needing array indexes in the path expression in future.
Update Just to be clear, I'm not asking for code samples - my question is whether there's existing code (built-in or library) I can re-use. Thanks to the contributors below for suggestions of code samples anyway! Note that none of them handle the array index requirement.
Doing a quick search, I came across JSONPath. Haven't used it at all, but it looks like it might do what you want it to.
Example usage:
var x = {foo: 42, bar: {fubar: true}}
var res1 = jsonPath(x, "$.bar.fubar"); // Array containing fubar's value
Why not try something like
function evalPath(obj, path)
{
var rtnValue = obj;
// Split our path into an array we can iterate over
var path = path.split(".");
for (var i = 0, max=path.length; i < max; i++)
{
// If setting current path to return value fails, set to null and break
if (typeof (rtnValue = rtnValue[path[i]]) == "undefined")
{
rtnValue = null;
break;
}
}
// Return the final path value, or null if it failed
return rtnValue;
}
Not tested, but it should work fairly well. Like XPath, it will return null if it can't find what it's looking for.
JavaScript provides eval, but I don't recommend it.
like
function locate(obj, path) {
var p = path.split("."), a = p.shift();
if(a in obj)
return p.length ? locate(obj[a], p.join(".")) : obj[a];
return undefined;
}
locate(x, "bar.fubar")
this works on the right only, of course
You could try something like this. I can't really think of a situation where it would be appropriate to store paths this way though.
function evalPath(obj, path) {
var pathLevels = path.split('.');
var result = obj;
for (var i = 0; i < pathLevels.length; i++) {
try {
result = result[pathLevels[i]];
}
catch (e) {
alert('Property not found:' + pathLevels[i]);
}
}
return result;
}
The alert is really only there for debugging purposes. You may want to return null or something.
How about:
evalPath = function(obj, path) {
if (path[0] === "[") {
return eval("obj" + path);
} else {
return eval("obj." + path);
}
};
This has the advantage that it works for arbitrary strings:
evalPath([1,2,3], "[0]"); => 1
evalPath({a:{b:7}}, "a.b"); => 7
This, of course, only works if you really trust your input.

Categories