How to convert an rss or atom xml feed to json
- Step 1Fetch the feed XML — Open the feed URL in a browser and save the source, or run
curl -o feed.xml https://example.com/feed.xml. Save the complete document including the root<rss>or<feed>element. - Step 2Open the XML to JSON tool — This is a Pro tool. Drop the saved
.xmlonto the dropzone. Most feeds are well under the free-tier 2 MB limit; full-text or archive feeds that are larger need signed-in Pro (100 MB per file). - Step 3Leave Strip namespaces off (usually) — Feeds use namespaces meaningfully —
content:,dc:,media:,atom:. Keep Strip namespaces OFF socontent:encodedandmedia:contentstay distinct. Only strip if your downstream code specifically wants bare keys and you have checked for collisions. - Step 4Set type coercion to taste — Feeds are mostly text, so coercion has little effect; an
<itunes:explicit>true</itunes:explicit>would become a boolean. Leave Coerce types on, or off for a uniformly-stringly output if your renderer prefers that. - Step 5Convert and find the items — Click Convert to JSON. RSS items are at
rss.channel.item; Atom entries atfeed.entry. With one item you get an object, with many an array — handle both. - Step 6Normalise the fields for your app — Because the tool does not unify RSS and Atom, write a small map: pick
pubDate || published,description || summary, and resolve the Atom<link>object's@href. Use json-key-renamer to standardise keys or json-path-extractor to pull just the item array.
RSS 2.0 vs Atom 1.0 — same data, different keys
The tool preserves each format's native names; it does NOT normalise them. Plan a small mapping layer in your app.
| Concept | RSS 2.0 JSON key | Atom 1.0 JSON key |
|---|---|---|
| Feed items | rss.channel.item | feed.entry |
| Item title | title (string) | title (string or object if type attr present) |
| Item link | link (string) | link (object {"@href":..,"@rel":..}) — read @href |
| Publish date | pubDate (RFC-822 string, raw) | published / updated (ISO-8601 string, raw) |
| Summary | description (string, CDATA unwrapped) | summary (string) |
| Full body | content:encoded (HTML string) | content (string or {"#text":..,"@type":"html"}) |
| Unique id | guid (string or {"#text","@isPermaLink"}) | id (string) |
Option matrix for feeds
Real controls and their effect on feed XML. attributePrefix (@) and textNodeName (#text) are fixed defaults.
| Option | Feed effect | Recommendation |
|---|---|---|
| Strip namespaces OFF | Keeps content:encoded, media:content, dc:creator, itunes:* distinct | Default — feeds rely on namespaces |
| Strip namespaces ON | media:content→content can collide with Atom <content>; dc:creator→creator | Avoid unless you have checked for collisions |
| Parse attributes ON | Keeps Atom link/@href, media:content/@url, enclosure @url/@length | Keep ON — you need these URLs |
| Coerce types | Mostly no-op; turns boolean-ish text (itunes:explicit) into booleans | Either — feeds are text-heavy |
Cookbook
Real RSS and Atom fragments with the exact JSON the converter returns, and the normalisation each needs.
An RSS 2.0 item
ExampleRSS items sit under rss.channel.item. The CDATA description is unwrapped to an HTML string; pubDate stays the raw RFC-822 string — the tool does not convert it to ISO-8601.
Input:
<rss version="2.0">
<channel>
<item>
<title>Hello</title>
<link>https://ex.com/p/1</link>
<pubDate>Mon, 01 Jun 2026 12:00:00 GMT</pubDate>
<description><![CDATA[<p>Body</p>]]></description>
</item>
</channel>
</rss>
Output:
{ "rss": { "channel": { "item": {
"title": "Hello",
"link": "https://ex.com/p/1",
"pubDate": "Mon, 01 Jun 2026 12:00:00 GMT", // RAW, not ISO
"description": "<p>Body</p>"
} } } }An Atom entry — link is an object, not a string
ExampleAtom puts the URL in a <link href> attribute, so the converter returns an object with @href, NOT a plain string. Read entry.link['@href'] (or the right element when there are several links).
Input:
<feed xmlns="http://www.w3.org/2005/Atom">
<entry>
<title>Hello</title>
<link rel="alternate" href="https://ex.com/p/1"/>
<published>2026-06-01T12:00:00Z</published>
<summary>Teaser</summary>
</entry>
</feed>
Output:
{ "feed": { "entry": {
"title": "Hello",
"link": { "@rel": "alternate", "@href": "https://ex.com/p/1" },
"published": "2026-06-01T12:00:00Z",
"summary": "Teaser"
} } }Normalising RSS and Atom to one shape in your app
ExampleBecause the tool does not unify the formats, write a tiny adapter. This is the deliberate normalisation a feed library hides — doing it yourself keeps it explicit and testable.
function normalise(item) {
return {
title: typeof item.title === 'object' ? item.title['#text'] : item.title,
url: item.link?.['@href'] ?? item.link, // Atom vs RSS
date: item.published ?? item.updated ?? item.pubDate,
body: item['content:encoded'] ?? item.summary ?? item.description,
};
}
// date is still a raw string here — parse with new Date(date)
// in JS or dateutil.parser.parse(date) in Python.Single item vs. many — the .map() trap
ExampleA feed with one item gives an object on the item key; many give an array. Aggregator code that calls .map() throws on a one-item feed. Always coalesce to an array first.
One item:
{ "rss": { "channel": { "item": { "title": "Only" } } } }
Many items:
{ "rss": { "channel": { "item": [ {..}, {..} ] } } }
Safe Next.js usage:
const raw = data.rss?.channel?.item ?? data.feed?.entry ?? [];
const items = (Array.isArray(raw) ? raw : [raw]).map(normalise);Podcast / Media RSS enclosures
ExamplePodcast feeds carry the audio in an <enclosure> and image in media:thumbnail. Keep Parse attributes on so the url/length/type attributes survive as @-keys; the tool preserves them but does not move them into a 'media' object for you.
Input:
<item>
<enclosure url="https://ex.com/ep1.mp3" length="5242880" type="audio/mpeg"/>
<media:thumbnail url="https://ex.com/cover.jpg"/>
</item>
Output (Parse attributes ON, Strip namespaces OFF):
{ "enclosure": { "@url": "https://ex.com/ep1.mp3",
"@length": 5242880, "@type": "audio/mpeg" },
"media:thumbnail": { "@url": "https://ex.com/cover.jpg" } }Errors and edge cases
Real errors and silent failures sourced from each platform's own documentation. Match the wording to the row, fix what the row says to fix.
Dates are NOT converted to ISO-8601
Not normalisedRSS pubDate stays a raw RFC-822 string (Mon, 01 Jun 2026 12:00:00 GMT); Atom published/updated stay ISO-8601. The tool does no date math. Parse in your app: new Date(item.pubDate) in JS, or dateutil.parser.parse(...) in Python, before sorting.
RSS and Atom keys are NOT unified
Not normalisedYou get the native keys: item vs entry, pubDate vs published, description vs summary, string link vs Atom link object. The converter is format-faithful, not a feed normaliser. Write a small adapter (see the cookbook) to map both into one shape.
Atom <link> is an object, not a string
Shape differenceAtom puts the URL in a href attribute, so link parses to {"@href":..,"@rel":..}. Entries with several links (alternate, enclosure, self) produce an ARRAY of link objects — pick the one with @rel=='alternate'. Code expecting a plain string URL will break.
Single item is an object, not an array
Singleton trapOne <item>/<entry> yields an object; multiple yield an array. .map() on the single-item object throws or iterates keys. Coalesce first: Array.isArray(x) ? x : [x]. This is the most common aggregator bug after switching to JSON.
Mixed-content descriptions lose inter-element spacing
Spacing lossIf a description holds text interleaved with inline tags that are NOT inside CDATA (<description>See <b>this</b> link</description>), the text fragments concatenate into #text with spaces dropped (Seelink). Well-formed feeds wrap HTML in CDATA, which is unwrapped cleanly — so this only bites malformed feeds.
Malformed feed XML parses without error
Silent parseMany real feeds are slightly invalid (unescaped &, unclosed tags). fast-xml-parser is lenient and may return a partially-wrong object instead of throwing. If items look truncated or missing, the source feed is likely malformed — validate it before relying on the JSON.
Stripping namespaces can merge media:content with Atom content
Key collisionWith Strip namespaces on, media:content becomes content, which can collide with Atom's <content> element and merge into an array. Keep Strip namespaces OFF for feeds so the Media RSS, Dublin Core, and iTunes namespaces stay distinct.
guid / id come through with their attributes
ExpectedRSS <guid isPermaLink="false"> parses to {"#text":..,"@isPermaLink":false} (an object) when it has attributes, or a plain string when it does not. Atom <id> is a plain string. Read item.guid?.['#text'] ?? item.guid to handle both shapes.
Frequently asked questions
Does the tool convert RSS dates to ISO-8601?
No. pubDate is returned as the raw RFC-822 string and Atom published/updated as their original ISO-8601 string — there is no date conversion. Parse and sort in your code: new Date(item.pubDate) in JS or dateutil.parser.parse() in Python.
Does it normalise RSS and Atom into one schema?
No. It is a faithful structural parser, not a feed-normalisation library. You get native keys (item/entry, pubDate/published, description/summary). Write a small adapter — there is a ready-made one in the cookbook above — or standardise keys afterward with json-key-renamer.
Why is my Atom link an object instead of a URL string?
Atom stores the URL in a href attribute, so <link rel="alternate" href="..."/> parses to {"@rel":"alternate","@href":"..."}. Read entry.link['@href']. When an entry has multiple <link> elements you get an array — pick the one with @rel=='alternate'.
How do I handle a feed with only one item?
A single <item>/<entry> becomes an object, not a one-element array, so .map() will break. Coalesce first: const items = Array.isArray(raw) ? raw : [raw]. Always test your aggregator against both a single-item and a multi-item feed.
What happens to CDATA descriptions and content:encoded?
The CDATA wrapper is removed and the inner HTML/text is delivered as a plain string — ready to render (after sanitising) or store. This applies to RSS <description>, content:encoded, and Atom <content>/<summary>.
Are podcast enclosures and media thumbnails preserved?
Yes, as long as Parse attributes is on. <enclosure url=.. length=.. type=..> becomes an object of @-keys, and media:content/media:thumbnail keep their @url attributes. The tool preserves them in place — it does not roll them into a single media object, so read them where they sit.
Should I enable Strip namespaces for feeds?
Usually no. Feeds rely on namespaces (content:, dc:, media:, itunes:) to keep fields distinct, and stripping can collide media:content with Atom <content>. Leave it off and reference the prefixed keys directly.
Can I get just the array of items without the channel wrapper?
Not from the converter — it returns the full document tree. After conversion, use json-path-extractor with $.rss.channel.item (RSS) or $.feed.entry (Atom) to pull just the items array.
Will it handle a malformed feed?
Often, but silently. fast-xml-parser is lenient and may parse an invalid feed (unescaped &, unclosed tags) into a partially-wrong object without throwing. If items look missing or truncated, validate the source feed — the wrongness comes from the input, and the tool will not flag it.
Is the feed content sent to JAD Apps?
No. Parsing runs entirely in your browser via fast-xml-parser. Private or authenticated feed URLs and article bodies never reach JAD Apps servers — only an anonymous run counter (no content) is recorded for signed-in dashboard stats.
How big a feed can I convert?
This is a Pro tool: 2 MB per file on the free tier, 100 MB per file on signed-in Pro. Standard feeds are tiny; only full-text archive feeds approach the free limit, in which case use Pro.
Can I turn the JSON back into a CSV for a spreadsheet?
Yes — after converting the feed, extract the items array with json-path-extractor ($.rss.channel.item), then feed that array to json-to-csv for a spreadsheet of titles, links, and dates. Normalise RSS/Atom keys first (see the cookbook) so the columns line up across formats.
Privacy first
Conversion runs locally in your browser. No file is uploaded — only metadata counters are saved for signed-in dashboard stats.