Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Undefined or missing elements parse as empty strings #110

Open
kierangraham opened this issue Jun 12, 2020 · 9 comments
Open

Undefined or missing elements parse as empty strings #110

kierangraham opened this issue Jun 12, 2020 · 9 comments

Comments

@kierangraham
Copy link

When parsing some XML which has optional elements, the values get returned as an empty string.

Included are the example output that is received and the desired output, plus the code used to generate.

It would be greatly appreciated for some pointers on how to produce the desired output and in any case thanks for the work on the library.

Example Output

[
  {
    "optional": "has a value"
  },
  {
    "optional": ""
  },
  {
    "optional": ""
  }
]

Desired Output

[
  {
    "optional": "has a value"
  },
  {
    "optional": null
  },
  {
    "optional": null
  }
]

Code

const xml = `
  <example>
    <entry>
      <optional>has a value</optional>
    </entry>

    <entry>
      <optional/>
    </entry>

    <entry/>
  </example>
`;

const result = await camaro.transform(xml, [
  '//entry',
  {
    optional: 'optional',
  },
]);

console.log(JSON.stringify(result, null, 2));
@tuananh
Copy link
Owner

tuananh commented Jun 13, 2020

this is expected behavior because xpath is returning empty string for that path.

another reason is that i want the output to shape the same as the template. eg: i don't want to check for null object from output before accessing it.

however, I can see null is a better value for cases like this. maybe an option for parse to keep missing path as null

lemme take a look into that.

in the mean time, there's a workaround for this with the example here

https://github.com/tuananh/camaro/blob/develop/examples/default-value.md

@kierangraham
Copy link
Author

Thanks for the reply @tuananh. I’ve seen that example before and wasn’t sure how it would be possible to have it return an actual null value vs a ”null” string value.

@tuananh
Copy link
Owner

tuananh commented Jun 15, 2020

Thanks for the reply @tuananh. I’ve seen that example before and wasn’t sure how it would be possible to have it return an actual null value vs a ”null” string value.

my bad, it isn't really a fix for that.

@tuananh
Copy link
Owner

tuananh commented Jun 15, 2020

Thanks for the reply @tuananh. I’ve seen that example before and wasn’t sure how it would be possible to have it return an actual null value vs a ”null” string value.

I need to think on this some more.

What do you think to handle case like calling xpath function? like upper-case(optional)? What should be returned in this case?

@kierangraham
Copy link
Author

Hmm I'm not sure, I've thought about this a little also as I was browsing the code, trying to understand how it'd be possible.

What if there was a special XPath function? Something like nullable()? If that's possible then maybe we could do something like nullable(upper-case(optional)) and get a null value back if the result is "".

I'm not sure how possible it is to do that, and does kind of pollute the XPath API somewhat, but I've not yet thought of something better.

At least with an approach like that, it would be possible to opt-in to receiving the null values so would be backwards compatible.

@tuananh
Copy link
Owner

tuananh commented Jun 16, 2020

I may lean into introducing a parse option , sth like below

await transform(xml, template, parseOptions)

but nothing concrete yet

@kierangraham
Copy link
Author

@tuananh any hints on how you would approach this? I’d be happy to try and contribute a PR if you could give some insights on your ideas to integrate the parseOptions.

One thing I wonder about is how to tell if an object should be null. I imagine it should be easier to determine if a string should be null or not but an object could be a little more complicated, maybe not.

In any case, any pointers would be appreciated and I’d see if I can use those to make the PR.

@tuananh
Copy link
Owner

tuananh commented Jul 2, 2020

@kierangraham that would require you to look into my pugixml source code to check if an xpath node exists or not.

and parsing the parse options of course.

@iki
Copy link

iki commented Nov 22, 2022

@kierangraham there may be also NaNs as a result of Xpath number() expressions, you can use stripNullish on the result data:

import { isArray, isObject, isUndefined, isNull, isNaN } from 'lodash';

export type Matcher<T = any> = (value: T) => boolean;

// Deeply remove matching values from an object or array
export const stripMatching = <T = any>(match: Matcher<T>) => {
  const noMatch = (value: T) => !match(value);
  const strip = <I = any, O = I>(data: I): O =>
    isArray(data)
      ? (data.filter(noMatch).map(strip) as O)
      : isObject(data)
      ? (Object.entries(data).reduce(
          (res, [k, v]) => (match(v) ? res : { ...res, [k]: strip(v) }),
          {},
        ) as O)
      : (data as unknown as O);
  return strip;
};

export const isFalsy = (value: any): value is false | null | undefined | '' | typeof NaN =>
  value == null || value === false || value === '' || isNaN(value);
export const isNullish = (value: any): value is null | undefined | '' | typeof NaN =>
  value == null || value === '' || isNaN(value);

export const stripFalsy = stripMatching(isFalsy);
export const stripNaN = stripMatching(isNaN);
export const stripNullish = stripMatching(isNullish);
export const stripNulls = stripMatching(isNull);
export const stripUndefined = stripMatching(isUndefined);

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants