Repository: mmcdole/gofeed
Pull request #154 (2020-09-03 13:52:16)

Follow up to #150: Added support for JSON feed

mmcdole: @nkanaev thanks for the PR!  Could you resolve the conflicts?  I'll start to review it now.
coveralls: 
[![Coverage Status](https://coveralls.io/builds/33231994/badge)](https://coveralls.io/builds/33231994)

Coverage increased (+3.01%) to 64.695% when pulling **dd5cd61e214fe521a227f1e258878e6a16640765 on nkanaev:json-feed** into **193d002a07e68dc1499453aaecf1b85a010ab95a on mmcdole:master**.

nkanaev: conflict resolved by:

```sh
$ git merge -s recursive -X theirs upstream/master  # upstream points to this repo
$ go test  # updates go.mod & go.sum
```

Patch:
diff --git a/README.md b/README.md
index 3d50265..a3473b7 100644
--- a/README.md
+++ b/README.md
@@ -1,10 +1,11 @@
-# gofeed 
+# gofeed
 
 [![Build Status](https://travis-ci.org/mmcdole/gofeed.svg?branch=master)](https://travis-ci.org/mmcdole/gofeed) [![Coverage Status](https://coveralls.io/repos/github/mmcdole/gofeed/badge.svg?branch=master)](https://coveralls.io/github/mmcdole/gofeed?branch=master) [![Go Report Card](https://goreportcard.com/badge/github.com/mmcdole/gofeed)](https://goreportcard.com/report/github.com/mmcdole/gofeed) [![](https://godoc.org/github.com/mmcdole/gofeed?status.svg)](http://godoc.org/github.com/mmcdole/gofeed) [![License](http://img.shields.io/:license-mit-blue.svg)](http://doge.mit-license.org)
 
-The `gofeed` library is a robust feed parser that supports parsing both [RSS](https://en.wikipedia.org/wiki/RSS) and [Atom](https://en.wikipedia.org/wiki/Atom_(standard)) feeds.  The library provides a universal `gofeed.Parser`  that will parse and convert all feed types into a hybrid `gofeed.Feed` model.  You also have the option of utilizing the feed specific `atom.Parser` or `rss.Parser` parsers which generate `atom.Feed` and `rss.Feed` respectively.
+The `gofeed` library is a robust feed parser that supports parsing both [RSS](https://en.wikipedia.org/wiki/RSS), [Atom](<https://en.wikipedia.org/wiki/Atom_(standard)>) and [JSON](https://jsonfeed.org/version/1) feeds. The library provides a universal `gofeed.Parser` that will parse and convert all feed types into a hybrid `gofeed.Feed` model. You also have the option of utilizing the feed specific `atom.Parser` or `rss.Parser` or `json.Parser` parsers which generate `atom.Feed`, `rss.Feed` and `json.Feed` respectively.
 
 ## Table of Contents
+
 - [Features](#features)
 - [Overview](#overview)
 - [Basic Usage](#basic-usage)
@@ -19,16 +20,18 @@ The `gofeed` library is a robust feed parser that supports parsing both [RSS](ht
 ## Features
 
 #### Supported feed types:
-* RSS 0.90
-* Netscape RSS 0.91
-* Userland RSS 0.91
-* RSS 0.92
-* RSS 0.93
-* RSS 0.94
-* RSS 1.0
-* RSS 2.0
-* Atom 0.3
-* Atom 1.0
+
+- RSS 0.90
+- Netscape RSS 0.91
+- Userland RSS 0.91
+- RSS 0.92
+- RSS 0.93
+- RSS 0.94
+- RSS 1.0
+- RSS 2.0
+- Atom 0.3
+- Atom 1.0
+- JSON 1.0
 
 #### Extension Support
 
@@ -38,35 +41,36 @@ It parses all other feed extensions in a generic way (see the [Extensions](#exte
 
 #### Invalid Feeds
 
-A best-effort attempt is made at parsing broken and invalid XML feeds.  Currently, `gofeed` can succesfully parse feeds with the following issues:
+A best-effort attempt is made at parsing broken and invalid XML feeds. Currently, `gofeed` can succesfully parse feeds with the following issues:
+
 - Unescaped/Naked Markup in feed elements
 - Undeclared namespace prefixes
 - Missing closing tags on certain elements
 - Illegal tags within feed elements without namespace prefixes
 - Missing "required" elements as specified by the respective feed specs.
-- Incorrect date formats 
+- Incorrect date formats
 
 ## Overview
 
-The `gofeed` library is comprised of a universal feed parser and several feed specific parsers.   Which one you choose depends entirely on your usecase.  If you will be handling both rss and atom feeds then it makes sense to use the `gofeed.Parser`.  If you know ahead of time that you will only be parsing one feed type then it would make sense to use `rss.Parser` or `atom.Parser`.
+The `gofeed` library is comprised of a universal feed parser and several feed specific parsers. Which one you choose depends entirely on your usecase. If you will be handling rss, atom and json feeds then it makes sense to use the `gofeed.Parser`. If you know ahead of time that you will only be parsing one feed type then it would make sense to use `rss.Parser` or `atom.Parser` or `json.Parser`.
 
 #### Universal Feed Parser
 
-The universal `gofeed.Parser` works in 3 stages: detection, parsing and translation.  It first detects the feed type that it is currently parsing.  Then it uses a feed specific parser to parse the feed into its true representation which will be either a `rss.Feed` or `atom.Feed`.  These models cover every field possible for their respective feed types.  Finally, they are *translated* into a `gofeed.Feed` model that is a hybrid of both feed types.  Performing the universal feed parsing in these 3 stages allows for more flexibility and keeps the code base more maintainable by separating RSS and Atom parsing into seperate packages.
+The universal `gofeed.Parser` works in 3 stages: detection, parsing and translation. It first detects the feed type that it is currently parsing. Then it uses a feed specific parser to parse the feed into its true representation which will be either a `rss.Feed` or `atom.Feed` or `json.Feed`. These models cover every field possible for their respective feed types. Finally, they are _translated_ into a `gofeed.Feed` model that is a hybrid of all feed types. Performing the universal feed parsing in these 3 stages allows for more flexibility and keeps the code base more maintainable by separating RSS, Atom and Json parsing into seperate packages.
 
 ![Diagram](docs/sequence.png)
 
-The translation step is done by anything which adheres to the `gofeed.Translator` interface.  The `DefaultRSSTranslator` and `DefaultAtomTranslator` are used behind the scenes when you use the `gofeed.Parser` with its default settings.  You can see how they translate fields from ```atom.Feed``` or ```rss.Feed``` to the universal ```gofeed.Feed``` struct in the [Default Mappings](#default-mappings) section.  However, should you disagree with the way certain fields are translated you can easily supply your own `gofeed.Translator` and override this behavior.  See the [Advanced Usage](#advanced-usage) section for an example how to do this.
+The translation step is done by anything which adheres to the `gofeed.Translator` interface. The `DefaultRSSTranslator`, `DefaultAtomTranslator`, `DefaultJSONTranslator` are used behind the scenes when you use the `gofeed.Parser` with its default settings. You can see how they translate fields from `atom.Feed` or `rss.Feed` `json.Feed` to the universal `gofeed.Feed` struct in the [Default Mappings](#default-mappings) section. However, should you disagree with the way certain fields are translated you can easily supply your own `gofeed.Translator` and override this behavior. See the [Advanced Usage](#advanced-usage) section for an example how to do this.
 
 #### Feed Specific Parsers
 
-The `gofeed` library provides two feed specific parsers: `atom.Parser` and `rss.Parser`.  If the hybrid `gofeed.Feed` model that the universal `gofeed.Parser` produces does not contain a field from the `atom.Feed` or `rss.Feed` model that you require, it might be beneficial to use the feed specific parsers.  When using the `atom.Parser` or `rss.Parser` directly, you can access all of fields found in the `atom.Feed` and `rss.Feed` models.  It is also marginally faster because you are able to skip the translation step.
+The `gofeed` library provides two feed specific parsers: `atom.Parser`, `rss.Parser` and `json.Parser`. If the hybrid `gofeed.Feed` model that the universal `gofeed.Parser` produces does not contain a field from the `atom.Feed` or `rss.Feed` or `json.Feed` model that you require, it might be beneficial to use the feed specific parsers. When using the `atom.Parser` or `rss.Parser` or `json.Parser` directly, you can access all of fields found in the `atom.Feed`, `rss.Feed` and `json.Feed` models. It is also marginally faster because you are able to skip the translation step.
 
 ## Basic Usage
 
 #### Universal Feed Parser
 
-The most common usage scenario will be to use ```gofeed.Parser``` to parse an arbitrary RSS or Atom feed into the hybrid ```gofeed.Feed``` model.  This hybrid model allows you to treat RSS and Atom feeds the same.
+The most common usage scenario will be to use `gofeed.Parser` to parse an arbitrary RSS or Atom or JSON feed into the hybrid `gofeed.Feed` model. This hybrid model allows you to treat RSS, Atom and JSON feeds the same.
 
 ##### Parse a feed from an URL:
 
@@ -111,7 +115,7 @@ fmt.Println(feed.Title)
 
 #### Feed Specific Parsers
 
-You can easily use the `rss.Parser` and `atom.Parser` directly if you have a usage scenario that requires it:
+You can easily use the `rss.Parser`, `atom.Parser` or `json.Parser` directly if you have a usage scenario that requires it:
 
 ##### Parse a RSS feed into a `rss.Feed`
 
@@ -137,13 +141,22 @@ atomFeed, _ := fp.Parse(strings.NewReader(feedData))
 fmt.Println(atomFeed.Subtitle)
 ```
 
+##### Parse a JSON feed into a `json.Feed`
+
+```go
+feedData := `{"version":"1.0", "home_page_url": "https://daringfireball.net"}`
+fp := json.Parser{}
+jsonFeed, _ := fp.Parse(strings.NewReader(feedData))
+fmt.Println(jsonFeed.HomePageURL)
+```
+
 ## Advanced Usage
 
 ##### Parse a feed while using a custom translator
 
-The mappings and precedence order that are outlined in the [Default Mappings](#default-mappings) section are provided by the following two structs: `DefaultRSSTranslator` and `DefaultAtomTranslator`.  If you have fields that you think should have a different precedence, or if you want to make a translator that is aware of an unsupported extension you can do this by specifying your own RSS or Atom translator when using the `gofeed.Parser`.
+The mappings and precedence order that are outlined in the [Default Mappings](#default-mappings) section are provided by the following two structs: `DefaultRSSTranslator`, `DefaultAtomTranslator` and `DefaultJSONTranslator`. If you have fields that you think should have a different precedence, or if you want to make a translator that is aware of an unsupported extension you can do this by specifying your own RSS or Atom or JSON translator when using the `gofeed.Parser`.
 
-Here is a simple example of creating a custom `Translator` that makes the `/rss/channel/itunes:author` field have a higher precedence than the `/rss/channel/managingEditor` field in RSS feeds.  We will wrap the existing `DefaultRSSTranslator` since we only want to change the behavior for a single field.
+Here is a simple example of creating a custom `Translator` that makes the `/rss/channel/itunes:author` field have a higher precedence than the `/rss/channel/managingEditor` field in RSS feeds. We will wrap the existing `DefaultRSSTranslator` since we only want to change the behavior for a single field.
 
 First we must define a custom translator:
 
@@ -162,7 +175,7 @@ type MyCustomTranslator struct {
 
 func NewMyCustomTranslator() *MyCustomTranslator {
   t := &MyCustomTranslator{}
-  
+
   // We create a DefaultRSSTranslator internally so we can wrap its Translate
   // call since we only want to modify the precedence for a single field.
   t.defaultTranslator = &gofeed.DefaultRSSTranslator{}
@@ -179,7 +192,7 @@ func (ct* MyCustomTranslator) Translate(feed interface{}) (*gofeed.Feed, error)
   if err != nil {
     return nil, err
   }
-  
+
   if rss.ITunesExt != nil && rss.ITunesExt.Author != "" {
       f.Author = rss.ITunesExt.Author
   } else {
@@ -198,59 +211,60 @@ feedData := `<rss version="2.0">
 <itunes:author>Valentine Wiggin</itunes:author>
 </channel>
 </rss>`
-    
+
 fp := gofeed.NewParser()
 fp.RSSTranslator = NewMyCustomTranslator()
 feed, _ := fp.ParseString(feedData)
 fmt.Println(feed.Author) // Valentine Wiggin
 ```
 
-## Extensions 
+## Extensions
 
-Every element which does not belong to the feed's default namespace is considered an extension by `gofeed`.  These are parsed and stored in a tree-like structure located at `Feed.Extensions` and `Item.Extensions`.  These fields should allow you to access and read any custom extension elements.
+Every element which does not belong to the feed's default namespace is considered an extension by `gofeed`. These are parsed and stored in a tree-like structure located at `Feed.Extensions` and `Item.Extensions`. These fields should allow you to access and read any custom extension elements.
 
-In addition to the generic handling of extensions, `gofeed` also has built in support for parsing certain popular extensions into their own structs for convenience.  It currently supports the [Dublin Core](http://dublincore.org/documents/dces/) and [Apple iTunes](https://help.apple.com/itc/podcasts_connect/#/itcb54353390) extensions which you can access at `Feed.ItunesExt`, `feed.DublinCoreExt` and `Item.ITunesExt` and `Item.DublinCoreExt`
+In addition to the generic handling of extensions, `gofeed` also has built in support for parsing certain popular extensions into their own structs for convenience. It currently supports the [Dublin Core](http://dublincore.org/documents/dces/) and [Apple iTunes](https://help.apple.com/itc/podcasts_connect/#/itcb54353390) extensions which you can access at `Feed.ItunesExt`, `feed.DublinCoreExt` and `Item.ITunesExt` and `Item.DublinCoreExt`
 
 ## Default Mappings
 
-The ```DefaultRSSTranslator``` and the ```DefaultAtomTranslator``` map the following ```rss.Feed``` and ```atom.Feed``` fields to their respective ```gofeed.Feed``` fields.  They are listed in order of precedence (highest to lowest):
-
-
-`gofeed.Feed` | RSS | Atom
---- | --- | ---
-Title | /rss/channel/title<br>/rdf:RDF/channel/title<br>/rss/channel/dc:title<br>/rdf:RDF/channel/dc:title | /feed/title
-Description | /rss/channel/description<br>/rdf:RDF/channel/description<br>/rss/channel/itunes:subtitle | /feed/subtitle<br>/feed/tagline
-Link | /rss/channel/link<br>/rdf:RDF/channel/link | /feed/link[@rel=”alternate”]/@href<br>/feed/link[not(@rel)]/@href
-FeedLink | /rss/channel/atom:link[@rel="self"]/@href<br>/rdf:RDF/channel/atom:link[@rel="self"]/@href | /feed/link[@rel="self"]/@href
-Updated | /rss/channel/lastBuildDate<br>/rss/channel/dc:date<br>/rdf:RDF/channel/dc:date | /feed/updated<br>/feed/modified
-Published | /rss/channel/pubDate |
-Author | /rss/channel/managingEditor<br>/rss/channel/webMaster<br>/rss/channel/dc:author<br>/rdf:RDF/channel/dc:author<br>/rss/channel/dc:creator<br>/rdf:RDF/channel/dc:creator<br>/rss/channel/itunes:author | /feed/author
-Language | /rss/channel/language<br>/rss/channel/dc:language<br>/rdf:RDF/channel/dc:language | /feed/@xml:lang
-Image | /rss/channel/image<br>/rdf:RDF/image<br>/rss/channel/itunes:image | /feed/logo
-Copyright | /rss/channel/copyright<br>/rss/channel/dc:rights<br>/rdf:RDF/channel/dc:rights | /feed/rights<br>/feed/copyright
-Generator | /rss/channel/generator | /feed/generator
-Categories | /rss/channel/category<br>/rss/channel/itunes:category<br>/rss/channel/itunes:keywords<br>/rss/channel/dc:subject<br>/rdf:RDF/channel/dc:subject | /feed/category
-
-
-`gofeed.Item` | RSS | Atom
---- | --- | ---
-Title | /rss/channel/item/title<br>/rdf:RDF/item/title<br>/rdf:RDF/item/dc:title<br>/rss/channel/item/dc:title | /feed/entry/title
-Description | /rss/channel/item/description<br>/rdf:RDF/item/description<br>/rss/channel/item/dc:description<br>/rdf:RDF/item/dc:description | /feed/entry/summary
-Content | /rss/channel/item/content:encoded | /feed/entry/content
-Link | /rss/channel/item/link<br>/rdf:RDF/item/link | /feed/entry/link[@rel=”alternate”]/@href<br>/feed/entry/link[not(@rel)]/@href
-Updated | /rss/channel/item/dc:date<br>/rdf:RDF/rdf:item/dc:date | /feed/entry/modified<br>/feed/entry/updated
-Published | /rss/channel/item/pubDate<br>/rss/channel/item/dc:date | /feed/entry/published<br>/feed/entry/issued
-Author | /rss/channel/item/author<br>/rss/channel/item/dc:author<br>/rdf:RDF/item/dc:author<br>/rss/channel/item/dc:creator<br>/rdf:RDF/item/dc:creator<br>/rss/channel/item/itunes:author | /feed/entry/author
-GUID |  /rss/channel/item/guid | /feed/entry/id
-Image | /rss/channel/item/itunes:image<br>/rss/channel/item/media:image |
-Categories | /rss/channel/item/category<br>/rss/channel/item/dc:subject<br>/rss/channel/item/itunes:keywords<br>/rdf:RDF/channel/item/dc:subject | /feed/entry/category
-Enclosures | /rss/channel/item/enclosure | /feed/entry/link[@rel=”enclosure”]
+The `DefaultRSSTranslator`, the `DefaultAtomTranslator` and the `DefaultJSONTranslator` map the following `rss.Feed`, `atom.Feed` and `json.Feed` fields to their respective `gofeed.Feed` fields. They are listed in order of precedence (highest to lowest):
+
+`gofeed.Feed` | RSS | Atom | JSON
+--- | --- | --- | --
+Title | /rss/channel/title<br>/rdf:RDF/channel/title<br>/rss/channel/dc:title<br>/rdf:RDF/channel/dc:title | /feed/title | /title
+Description | /rss/channel/description<br>/rdf:RDF/channel/description<br>/rss/channel/itunes:subtitle | /feed/subtitle<br>/feed/tagline | /description
+Link | /rss/channel/link<br>/rdf:RDF/channel/link | /feed/link[@rel=”alternate”]/@href<br>/feed/link[not(@rel)]/@href | /home_page_url
+FeedLink | /rss/channel/atom:link[@rel="self"]/@href<br>/rdf:RDF/channel/atom:link[@rel="self"]/@href | /feed/link[@rel="self"]/@href | /feed_url
+Updated | /rss/channel/lastBuildDate<br>/rss/channel/dc:date<br>/rdf:RDF/channel/dc:date | /feed/updated<br>/feed/modified | /items[0]/date_modified
+Published | /rss/channel/pubDate | | /items[0]/date_published
+Author | /rss/channel/managingEditor<br>/rss/channel/webMaster<br>/rss/channel/dc:author<br>/rdf:RDF/channel/dc:author<br>/rss/channel/dc:creator<br>/rdf:RDF/channel/dc:creator<br>/rss/channel/itunes:author | /feed/author | /author/name
+Language | /rss/channel/language<br>/rss/channel/dc:language<br>/rdf:RDF/channel/dc:language | /feed/@xml:lang |
+Image | /rss/channel/image<br>/rdf:RDF/image<br>/rss/channel/itunes:image | /feed/logo | /icon
+Copyright | /rss/channel/copyright<br>/rss/channel/dc:rights<br>/rdf:RDF/channel/dc:rights | /feed/rights<br>/feed/copyright |
+Generator | /rss/channel/generator | /feed/generator |
+Categories | /rss/channel/category<br>/rss/channel/itunes:category<br>/rss/channel/itunes:keywords<br>/rss/channel/dc:subject<br>/rdf:RDF/channel/dc:subject | /feed/category |
+
+
+`gofeed.Item` | RSS | Atom | JSON
+--- | --- | --- | ---
+Title | /rss/channel/item/title<br>/rdf:RDF/item/title<br>/rdf:RDF/item/dc:title<br>/rss/channel/item/dc:title | /feed/entry/title | /items/title
+Description | /rss/channel/item/description<br>/rdf:RDF/item/description<br>/rss/channel/item/dc:description<br>/rdf:RDF/item/dc:description | /feed/entry/summary | /items/summary
+Content | /rss/channel/item/content:encoded | /feed/entry/content | /items/content_html
+Link | /rss/channel/item/link<br>/rdf:RDF/item/link | /feed/entry/link[@rel=”alternate”]/@href<br>/feed/entry/link[not(@rel)]/@href | /items/url
+Updated | /rss/channel/item/dc:date<br>/rdf:RDF/rdf:item/dc:date | /feed/entry/modified<br>/feed/entry/updated | /items/date_modified
+Published | /rss/channel/item/pubDate<br>/rss/channel/item/dc:date | /feed/entry/published<br>/feed/entry/issued | /items/date_published
+Author | /rss/channel/item/author<br>/rss/channel/item/dc:author<br>/rdf:RDF/item/dc:author<br>/rss/channel/item/dc:creator<br>/rdf:RDF/item/dc:creator<br>/rss/channel/item/itunes:author | /feed/entry/author | /items/author/name
+GUID |  /rss/channel/item/guid | /feed/entry/id | /items/id
+Image | /rss/channel/item/itunes:image<br>/rss/channel/item/media:image | | /items/image<br>/items/banner_image
+Categories | /rss/channel/item/category<br>/rss/channel/item/dc:subject<br>/rss/channel/item/itunes:keywords<br>/rdf:RDF/channel/item/dc:subject | /feed/entry/category | /items/tags
+Enclosures | /rss/channel/item/enclosure | /feed/entry/link[@rel=”enclosure”] | /items/attachments
+
 
 ## Dependencies
 
-* [goxpp](https://github.com/mmcdole/goxpp) - XML Pull Parser
-* [goquery](https://github.com/PuerkitoBio/goquery) - Go jQuery-like interface
-* [testify](https://github.com/stretchr/testify) - Unit test enhancements
+- [goxpp](https://github.com/mmcdole/goxpp) - XML Pull Parser
+- [goquery](https://github.com/PuerkitoBio/goquery) - Go jQuery-like interface
+- [testify](https://github.com/stretchr/testify) - Unit test enhancements
+- [jsoniter](https://github.com/json-iterator/go) - Faster JSON Parsing
 
 ## License
 
@@ -258,8 +272,9 @@ This project is licensed under the [MIT License](https://raw.githubusercontent.c
 
 ## Credits
 
-* [cristoper](https://github.com/cristoper) for his work on implementing xml:base relative URI handling.
-* [Mark Pilgrim](https://en.wikipedia.org/wiki/Mark_Pilgrim) and [Kurt McKee](http://kurtmckee.org) for their work on the excellent [Universal Feed Parser](https://github.com/kurtmckee/feedparser) Python library.  This library was the inspiration for the `gofeed` library.
-* [Dan MacTough](http://blog.mact.me) for his work on [node-feedparser](https://github.com/danmactough/node-feedparser).  It provided inspiration for the set of fields that should be covered in the hybrid `gofeed.Feed` model.
-* [Matt Jibson](https://mattjibson.com/) for his date parsing function in the [goread](https://github.com/mjibson/goread) project.
-* [Jim Teeuwen](https://github.com/jteeuwen) for his method of representing arbitrary feed extensions in the [go-pkg-rss](https://github.com/jteeuwen/go-pkg-rss) library.
+- [cristoper](https://github.com/cristoper) for his work on implementing xml:base relative URI handling.
+- [Mark Pilgrim](https://en.wikipedia.org/wiki/Mark_Pilgrim) and [Kurt McKee](http://kurtmckee.org) for their work on the excellent [Universal Feed Parser](https://github.com/kurtmckee/feedparser) Python library. This library was the inspiration for the `gofeed` library.
+- [Dan MacTough](http://blog.mact.me) for his work on [node-feedparser](https://github.com/danmactough/node-feedparser). It provided inspiration for the set of fields that should be covered in the hybrid `gofeed.Feed` model.
+- [Matt Jibson](https://mattjibson.com/) for his date parsing function in the [goread](https://github.com/mjibson/goread) project.
+- [Jim Teeuwen](https://github.com/jteeuwen) for his method of representing arbitrary feed extensions in the [go-pkg-rss](https://github.com/jteeuwen/go-pkg-rss) library.
+- [Sudhanshu Raheja](https://revolt.ist) for supporting JSON Feed parser
diff --git a/detector.go b/detector.go
index a9e665e..898b1af 100644
--- a/detector.go
+++ b/detector.go
@@ -1,9 +1,11 @@
 package gofeed
 
 import (
+	"bytes"
 	"io"
 	"strings"
 
+	jsoniter "github.com/json-iterator/go"
 	"github.com/mmcdole/gofeed/internal/shared"
 	xpp "github.com/mmcdole/goxpp"
 )
@@ -20,29 +22,57 @@ const (
 	FeedTypeAtom
 	// FeedTypeRSS represents an RSS feed
 	FeedTypeRSS
+	// FeedTypeJSON represents a JSON feed
+	FeedTypeJSON
 )
 
 // DetectFeedType attempts to determine the type of feed
 // by looking for specific xml elements unique to the
 // various feed types.
 func DetectFeedType(feed io.Reader) FeedType {
-	p := xpp.NewXMLPullParser(feed, false, shared.NewReaderLabel)
+	buffer := new(bytes.Buffer)
+	buffer.ReadFrom(feed)
 
-	xmlBase := shared.XMLBase{}
-	_, err := xmlBase.FindRoot(p)
-	if err != nil {
-		return FeedTypeUnknown
+	// remove leading whitespace (if exists)
+	var firstChar byte
+	for {
+		ch, err := buffer.ReadByte()
+		if err != nil {
+			return FeedTypeUnknown
+		}
+		if ch != ' ' && ch != '\t' {
+			firstChar = ch
+			buffer.UnreadByte()
+			break
+		}
 	}
 
-	name := strings.ToLower(p.Name)
-	switch name {
-	case "rdf":
-		return FeedTypeRSS
-	case "rss":
-		return FeedTypeRSS
-	case "feed":
-		return FeedTypeAtom
-	default:
-		return FeedTypeUnknown
+	if firstChar == '<' {
+		// Check if it's an XML based feed
+		p := xpp.NewXMLPullParser(bytes.NewReader(buffer.Bytes()), false, shared.NewReaderLabel)
+
+		xmlBase := shared.XMLBase{}
+		_, err := xmlBase.FindRoot(p)
+		if err != nil {
+			return FeedTypeUnknown
+		}
+
+		name := strings.ToLower(p.Name)
+		switch name {
+		case "rdf":
+			return FeedTypeRSS
+		case "rss":
+			return FeedTypeRSS
+		case "feed":
+			return FeedTypeAtom
+		default:
+			return FeedTypeUnknown
+		}
+	} else if firstChar == '{' {
+		// Check if document is valid JSON
+		if jsoniter.Valid(buffer.Bytes()) {
+			return FeedTypeJSON
+		}
 	}
+	return FeedTypeUnknown
 }
diff --git a/go.mod b/go.mod
index 2b7a732..0d96d76 100644
--- a/go.mod
+++ b/go.mod
@@ -5,8 +5,9 @@ go 1.14
 require (
 	github.com/PuerkitoBio/goquery v1.5.1
 	github.com/davecgh/go-spew v1.1.1 // indirect
+	github.com/json-iterator/go v1.1.10
 	github.com/mmcdole/goxpp v0.0.0-20181012175147-0068e33feabf
-	github.com/stretchr/testify v1.2.2
+	github.com/stretchr/testify v1.3.0
 	github.com/urfave/cli v1.22.3
 	golang.org/x/net v0.0.0-20200301022130-244492dfa37a
 	golang.org/x/text v0.3.2
diff --git a/go.sum b/go.sum
index 9ce04c9..e031582 100644
--- a/go.sum
+++ b/go.sum
@@ -5,18 +5,29 @@ github.com/andybalholm/cascadia v1.1.0 h1:BuuO6sSfQNFRu1LppgbD25Hr2vLYW25JvxHs5z
 github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y=
 github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY=
 github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
+github.com/json-iterator/go v1.1.10 h1:Kz6Cvnvv2wGdaG/V8yMvfkmNiXq9Ya2KUv4rouJJr68=
+github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
 github.com/mmcdole/goxpp v0.0.0-20181012175147-0068e33feabf h1:sWGE2v+hO0Nd4yFU/S/mDBM5plIU8v/Qhfz41hkDIAI=
 github.com/mmcdole/goxpp v0.0.0-20181012175147-0068e33feabf/go.mod h1:pasqhqstspkosTneA62Nc+2p9SOBBYAPbnmRRWPQ0V8=
+github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc=
+github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg=
+github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=
 github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
 github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
 github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
 github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
+github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
+github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
 github.com/urfave/cli v1.22.3 h1:FpNT6zq26xNpHZy08emi755QwzLPs6Pukqjlc7RfOMU=
 github.com/urfave/cli v1.22.3/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
diff --git a/json/feed.go b/json/feed.go
new file mode 100644
index 0000000..8539fce
--- /dev/null
+++ b/json/feed.go
@@ -0,0 +1,62 @@
+package json
+
+import "encoding/json"
+
+// Feed describes the structure for JSON Feed v1.0
+// https://www.jsonfeed.org/version/1/
+type Feed struct {
+	Version     string  `json:"version"`                 // version (required, string) is the URL of the version of the format the feed uses
+	Title       string  `json:"title,omitempty"`         // title (required, string) is the name of the feed
+	HomePageURL string  `json:"home_page_url,omitempty"` // home_page_url (optional but strongly recommended, string) is the URL of the resource that the feed describes. This resource should be an HTML page
+	FeedURL     string  `json:"feed_url,omitempty"`      // feed_url (optional but strongly recommended, string) is the URL of the feed, and serves as the unique identifier for the feed
+	Description string  `json:"description,omitempty"`   // description (optional, string)
+	UserComment string  `json:"user_comment,omitempty"`  // user_comment (optional, string) is a description of the purpose of the feed. This is for the use of people looking at the raw JSON, and should be ignored by feed readers.
+	NextURL     string  `json:"next_url,omitempty"`      // next_url (optional, string) is the URL of a feed that provides the next n items. This allows for pagination
+	Icon        string  `json:"icon,omitempty"`          // icon (optional, string) is the URL of an image for the feed suitable to be used in a timeline. It should be square and relatively large — such as 512 x 512
+	Favicon     string  `json:"favicon,omitempty"`       // favicon (optional, string) is the URL of an image for the feed suitable to be used in a source list. It should be square and relatively small, but not smaller than 64 x 64
+	Author      *Author `json:"author,omitempty"`        // author (optional, object) specifies the feed author. The author object has several members. These are all optional — but if you provide an author object, then at least one is required:
+	Expired     bool    `json:"expired,omitempty"`       // expired (optional, boolean) says whether or not the feed is finished — that is, whether or not it will ever update again.
+	Items       []*Item `json:"items"`                   // items is an array, and is required
+	// TODO Hubs // hubs (very optional, array of objects) describes endpoints that can be used to subscribe to real-time notifications from the publisher of this feed. Each object has a type and url, both of which are required. See the section “Subscribing to Real-time Notifications” below for details.
+	// TODO Extensions
+}
+
+func (f Feed) String() string {
+	json, _ := json.MarshalIndent(f, "", "    ")
+	return string(json)
+}
+
+// Item defines an item in the feed
+type Item struct {
+	ID            string         `json:"id,omitempty"`             // id (required, string) is unique for that item for that feed over time. If an id is presented as a number or other type, a JSON Feed reader must coerce it to a string. Ideally, the id is the full URL of the resource described by the item, since URLs make great unique identifiers.
+	URL           string         `json:"url,omitempty"`            // url (optional, string) is the URL of the resource described by the item. It’s the permalink
+	ExternalURL   string         `json:"external_url,omitempty"`   // external_url (very optional, string) is the URL of a page elsewhere. This is especially useful for linkblogs
+	Title         string         `json:"title,omitempty"`          // title (optional, string) is plain text. Microblog items in particular may omit titles.
+	ContentHTML   string         `json:"content_html,omitempty"`   // content_html and content_text are each optional strings — but one or both must be present. This is the HTML or plain text of the item. Important: the only place HTML is allowed in this format is in content_html. A Twitter-like service might use content_text, while a blog might use content_html. Use whichever makes sense for your resource. (It doesn’t even have to be the same for each item in a feed.)
+	ContentText   string         `json:"content_text,omitempty"`   // Same as above
+	Summary       string         `json:"summary,omitempty"`        // summary (optional, string) is a plain text sentence or two describing the item.
+	Image         string         `json:"image,omitempty"`          // image (optional, string) is the URL of the main image for the item. This image may also appear in the content_html
+	BannerImage   string         `json:"banner_image,omitempty"`   // banner_image (optional, string) is the URL of an image to use as a banner.
+	DatePublished string         `json:"date_published,omitempty"` // date_published (optional, string) specifies the date in RFC 3339 format. (Example: 2010-02-07T14:04:00-05:00.)
+	DateModified  string         `json:"date_modified,omitempty"`  // date_modified (optional, string) specifies the modification date in RFC 3339 format.
+	Author        *Author        `json:"author,omitempty"`         // author (optional, object) has the same structure as the top-level author. If not specified in an item, then the top-level author, if present, is the author of the item.
+	Tags          []string       `json:"tags,omitempty"`           // tags (optional, array of strings) can have any plain text values you want. Tags tend to be just one word, but they may be anything.
+	Attachments   *[]Attachments `json:"attachments,omitempty"`    // attachments (optional, array) lists related resources. Podcasts, for instance, would include an attachment that’s an audio or video file. An individual item may have one or more attachments.
+	// TODO Extensions
+}
+
+// Author defines the feed author structure. The author object has several members. These are all optional — but if you provide an author object, then at least one is required:
+type Author struct {
+	Name   string `json:"name,omitempty"`   // name (optional, string) is the author’s name.
+	URL    string `json:"url,omitempty"`    // url (optional, string) is the URL of a site owned by the author
+	Avatar string `json:"avatar,omitempty"` // avatar (optional, string) is the URL for an image for the author. It should be square and relatively large — such as 512 x 512
+}
+
+// Attachments defines the structure for related sources. Podcasts, for instance, would include an attachment that’s an audio or video file
+type Attachments struct {
+	URL               string `json:"url,omitempty"`                 // url (required, string) specifies the location of the attachment.
+	MimeType          string `json:"mime_type,omitempty"`           // mime_type (required, string) specifies the type of the attachment, such as “audio/mpeg.”
+	Title             string `json:"title,omitempty"`               // title (optional, string) is a name for the attachment. Important: if there are multiple attachments, and two or more have the exact same title (when title is present), then they are considered as alternate representations of the same thing. In this way a podcaster, for instance, might provide an audio recording in different formats.
+	SizeInBytes       int64  `json:"size_in_bytes,omitempty"`       // size_in_bytes (optional, number) specifies how large the file is.
+	DurationInSeconds int64  `json:"duration_in_seconds,omitempty"` // duration_in_seconds (optional, number) specifies how long it takes to listen to or watch, when played at normal speed.
+}
diff --git a/json/parser.go b/json/parser.go
new file mode 100644
index 0000000..4ccfa3e
--- /dev/null
+++ b/json/parser.go
@@ -0,0 +1,29 @@
+package json
+
+import (
+	"bytes"
+	"io"
+
+	jsoniter "github.com/json-iterator/go"
+)
+
+var (
+	j = jsoniter.ConfigCompatibleWithStandardLibrary
+)
+
+// Parser is an JSON Feed Parser
+type Parser struct{}
+
+// Parse parses an json feed into an json.Feed
+func (ap *Parser) Parse(feed io.Reader) (*Feed, error) {
+	jsonFeed := &Feed{}
+
+	buffer := new(bytes.Buffer)
+	buffer.ReadFrom(feed)
+
+	err := j.Unmarshal(buffer.Bytes(), jsonFeed)
+	if err != nil {
+		return nil, err
+	}
+	return jsonFeed, err
+}
diff --git a/parser.go b/parser.go
index 273b466..6c6131a 100644
--- a/parser.go
+++ b/parser.go
@@ -10,6 +10,7 @@ import (
 	"strings"
 
 	"github.com/mmcdole/gofeed/atom"
+	"github.com/mmcdole/gofeed/json"
 	"github.com/mmcdole/gofeed/rss"
 )
 
@@ -33,9 +34,11 @@ func (err HTTPError) Error() string {
 type Parser struct {
 	AtomTranslator Translator
 	RSSTranslator  Translator
+	JSONTranslator Translator
 	Client         *http.Client
 	rp             *rss.Parser
 	ap             *atom.Parser
+	jp             *json.Parser
 }
 
 // NewParser creates a universal feed parser.
@@ -43,13 +46,14 @@ func NewParser() *Parser {
 	fp := Parser{
 		rp: &rss.Parser{},
 		ap: &atom.Parser{},
+		jp: &json.Parser{},
 	}
 	return &fp
 }
 
-// Parse parses a RSS or Atom feed into
+// Parse parses a RSS or Atom or JSON feed into
 // the universal gofeed.Feed.  It takes an
-// io.Reader which should return the xml content.
+// io.Reader which should return the xml/json content.
 func (f *Parser) Parse(feed io.Reader) (*Feed, error) {
 	// Wrap the feed io.Reader in a io.TeeReader
 	// so we can capture all the bytes read by the
@@ -69,6 +73,8 @@ func (f *Parser) Parse(feed io.Reader) (*Feed, error) {
 		return f.parseAtomFeed(r)
 	case FeedTypeRSS:
 		return f.parseRSSFeed(r)
+	case FeedTypeJSON:
+		return f.parseJSONFeed(r)
 	}
 
 	return nil, ErrFeedTypeNotDetected
@@ -140,6 +146,14 @@ func (f *Parser) parseRSSFeed(feed io.Reader) (*Feed, error) {
 	return f.rssTrans().Translate(rf)
 }
 
+func (f *Parser) parseJSONFeed(feed io.Reader) (*Feed, error) {
+	jf, err := f.jp.Parse(feed)
+	if err != nil {
+		return nil, err
+	}
+	return f.jsonTrans().Translate(jf)
+}
+
 func (f *Parser) atomTrans() Translator {
 	if f.AtomTranslator != nil {
 		return f.AtomTranslator
@@ -156,6 +170,14 @@ func (f *Parser) rssTrans() Translator {
 	return f.RSSTranslator
 }
 
+func (f *Parser) jsonTrans() Translator {
+	if f.JSONTranslator != nil {
+		return f.JSONTranslator
+	}
+	f.JSONTranslator = &DefaultJSONTranslator{}
+	return f.JSONTranslator
+}
+
 func (f *Parser) httpClient() *http.Client {
 	if f.Client != nil {
 		return f.Client
diff --git a/translator.go b/translator.go
index cfe79a9..72942a2 100644
--- a/translator.go
+++ b/translator.go
@@ -8,10 +8,11 @@ import (
 	"github.com/mmcdole/gofeed/atom"
 	ext "github.com/mmcdole/gofeed/extensions"
 	"github.com/mmcdole/gofeed/internal/shared"
+	"github.com/mmcdole/gofeed/json"
 	"github.com/mmcdole/gofeed/rss"
 )
 
-// Translator converts a particular feed (atom.Feed or rss.Feed)
+// Translator converts a particular feed (atom.Feed or rss.Feed of json.Feed)
 // into the generic Feed struct
 type Translator interface {
 	Translate(feed interface{}) (*Feed, error)
@@ -684,3 +685,264 @@ func (t *DefaultAtomTranslator) firstPerson(persons []*atom.Person) (person *ato
 	person = persons[0]
 	return
 }
+
+// DefaultJSONTranslator converts an json.Feed struct
+// into the generic Feed struct.
+//
+// This default implementation defines a set of
+// mapping rules between json.Feed -> Feed
+// for each of the fields in Feed.
+type DefaultJSONTranslator struct{}
+
+// Translate converts an JSON feed into the universal
+// feed type.
+func (t *DefaultJSONTranslator) Translate(feed interface{}) (*Feed, error) {
+	json, found := feed.(*json.Feed)
+	if !found {
+		return nil, fmt.Errorf("Feed did not match expected type of *json.Feed")
+	}
+
+	result := &Feed{}
+	result.FeedVersion = json.Version
+	result.Title = t.translateFeedTitle(json)
+	result.Link = t.translateFeedLink(json)
+	result.FeedLink = t.translateFeedFeedLink(json)
+	result.Description = t.translateFeedDescription(json)
+	result.Image = t.translateFeedImage(json)
+	result.Author = t.translateFeedAuthor(json)
+	result.Items = t.translateFeedItems(json)
+	result.Updated = t.translateFeedUpdated(json)
+	result.UpdatedParsed = t.translateFeedUpdatedParsed(json)
+	result.Published = t.translateFeedPublished(json)
+	result.PublishedParsed = t.translateFeedPublishedParsed(json)
+	result.FeedType = "json"
+	// TODO UserComment is missing in global Feed
+	// TODO NextURL is missing in global Feed
+	// TODO Favicon is missing in global Feed
+	// TODO Exipred is missing in global Feed
+	// TODO Hubs is not supported in json.Feed
+	// TODO Extensions is not supported in json.Feed
+	return result, nil
+}
+
+func (t *DefaultJSONTranslator) translateFeedItem(jsonItem *json.Item) (item *Item) {
+	item = &Item{}
+	item.GUID = t.translateItemGUID(jsonItem)
+	item.Link = t.translateItemLink(jsonItem)
+	item.Title = t.translateItemTitle(jsonItem)
+	item.Content = t.translateItemContent(jsonItem)
+	item.Description = t.translateItemDescription(jsonItem)
+	item.Image = t.translateItemImage(jsonItem)
+	item.Published = t.translateItemPublished(jsonItem)
+	item.PublishedParsed = t.translateItemPublishedParsed(jsonItem)
+	item.Updated = t.translateItemUpdated(jsonItem)
+	item.UpdatedParsed = t.translateItemUpdatedParsed(jsonItem)
+	item.Author = t.translateItemAuthor(jsonItem)
+	item.Categories = t.translateItemCategories(jsonItem)
+	item.Enclosures = t.translateItemEnclosures(jsonItem)
+	// TODO ExternalURL is missing in global Feed
+	// TODO BannerImage is missing in global Feed
+	return
+}
+
+func (t *DefaultJSONTranslator) translateFeedTitle(json *json.Feed) (title string) {
+	if json.Title != "" {
+		title = json.Title
+	}
+	return
+}
+
+func (t *DefaultJSONTranslator) translateFeedDescription(json *json.Feed) (desc string) {
+	return json.Description
+}
+
+func (t *DefaultJSONTranslator) translateFeedLink(json *json.Feed) (link string) {
+	if json.HomePageURL != "" {
+		link = json.HomePageURL
+	}
+	return
+}
+
+func (t *DefaultJSONTranslator) translateFeedFeedLink(json *json.Feed) (link string) {
+	if json.FeedURL != "" {
+		link = json.FeedURL
+	}
+	return
+}
+
+func (t *DefaultJSONTranslator) translateFeedUpdated(json *json.Feed) (updated string) {
+	if len(json.Items) > 0 {
+		updated = json.Items[0].DateModified
+	}
+	return
+}
+
+func (t *DefaultJSONTranslator) translateFeedUpdatedParsed(json *json.Feed) (updated *time.Time) {
+	if len(json.Items) > 0 {
+		updateTime, err := shared.ParseDate(json.Items[0].DateModified)
+		if err == nil {
+			updated = &updateTime
+		}
+	}
+	return
+}
+
+func (t *DefaultJSONTranslator) translateFeedPublished(json *json.Feed) (published string) {
+	if len(json.Items) > 0 {
+		published = json.Items[0].DatePublished
+	}
+	return
+}
+
+func (t *DefaultJSONTranslator) translateFeedPublishedParsed(json *json.Feed) (published *time.Time) {
+	if len(json.Items) > 0 {
+		publishTime, err := shared.ParseDate(json.Items[0].DatePublished)
+		if err == nil {
+			published = &publishTime
+		}
+	}
+	return
+}
+
+func (t *DefaultJSONTranslator) translateFeedAuthor(json *json.Feed) (author *Person) {
+	if json.Author != nil {
+		name, address := shared.ParseNameAddress(json.Author.Name)
+		author = &Person{}
+		author.Name = name
+		author.Email = address
+	}
+	// Author.URL is missing in global feed
+	// Author.Avatar is missing in global feed
+	return
+}
+
+func (t *DefaultJSONTranslator) translateFeedImage(json *json.Feed) (image *Image) {
+	// Using the Icon rather than the image
+	// icon (optional, string) is the URL of an image for the feed suitable to be used in a timeline. It should be square and relatively large — such as 512 x 512
+	if json.Icon != "" {
+		image = &Image{}
+		image.URL = json.Icon
+	}
+	return
+}
+
+func (t *DefaultJSONTranslator) translateFeedItems(json *json.Feed) (items []*Item) {
+	items = []*Item{}
+	for _, i := range json.Items {
+		items = append(items, t.translateFeedItem(i))
+	}
+	return
+}
+
+func (t *DefaultJSONTranslator) translateItemTitle(jsonItem *json.Item) (title string) {
+	if jsonItem.Title != "" {
+		title = jsonItem.Title
+	}
+	return
+}
+
+func (t *DefaultJSONTranslator) translateItemDescription(jsonItem *json.Item) (desc string) {
+	if jsonItem.Summary != "" {
+		desc = jsonItem.Summary
+	}
+	return
+}
+
+func (t *DefaultJSONTranslator) translateItemContent(jsonItem *json.Item) (content string) {
+	if jsonItem.ContentHTML != "" {
+		content = jsonItem.ContentHTML
+	} else if jsonItem.ContentText != "" {
+		content = jsonItem.ContentText
+	}
+	return
+}
+
+func (t *DefaultJSONTranslator) translateItemLink(jsonItem *json.Item) (link string) {
+	return jsonItem.URL
+}
+
+func (t *DefaultJSONTranslator) translateItemUpdated(jsonItem *json.Item) (updated string) {
+	if jsonItem.DateModified != "" {
+		updated = jsonItem.DateModified
+	}
+	return updated
+}
+
+func (t *DefaultJSONTranslator) translateItemUpdatedParsed(jsonItem *json.Item) (updated *time.Time) {
+	if jsonItem.DateModified != "" {
+		updatedTime, err := shared.ParseDate(jsonItem.DateModified)
+		if err == nil {
+			updated = &updatedTime
+		}
+	}
+	return
+}
+
+func (t *DefaultJSONTranslator) translateItemPublished(jsonItem *json.Item) (pubDate string) {
+	if jsonItem.DatePublished != "" {
+		pubDate = jsonItem.DatePublished
+	}
+	return
+}
+
+func (t *DefaultJSONTranslator) translateItemPublishedParsed(jsonItem *json.Item) (pubDate *time.Time) {
+	if jsonItem.DatePublished != "" {
+		publishTime, err := shared.ParseDate(jsonItem.DatePublished)
+		if err == nil {
+			pubDate = &publishTime
+		}
+	}
+	return
+}
+
+func (t *DefaultJSONTranslator) translateItemAuthor(jsonItem *json.Item) (author *Person) {
+	if jsonItem.Author != nil {
+		name, address := shared.ParseNameAddress(jsonItem.Author.Name)
+		author = &Person{}
+		author.Name = name
+		author.Email = address
+	}
+	// Author.URL is missing in global feed
+	// Author.Avatar is missing in global feed
+	return
+}
+
+func (t *DefaultJSONTranslator) translateItemGUID(jsonItem *json.Item) (guid string) {
+	if jsonItem.ID != "" {
+		guid = jsonItem.ID
+	}
+	return
+}
+
+func (t *DefaultJSONTranslator) translateItemImage(jsonItem *json.Item) (image *Image) {
+	if jsonItem.Image != "" {
+		image = &Image{}
+		image.URL = jsonItem.Image
+	} else if jsonItem.BannerImage != "" {
+		image = &Image{}
+		image.URL = jsonItem.BannerImage
+	}
+	return
+}
+
+func (t *DefaultJSONTranslator) translateItemCategories(jsonItem *json.Item) (categories []string) {
+	if len(jsonItem.Tags) > 0 {
+		categories = jsonItem.Tags
+	}
+	return
+}
+
+func (t *DefaultJSONTranslator) translateItemEnclosures(jsonItem *json.Item) (enclosures []*Enclosure) {
+	if jsonItem.Attachments != nil {
+		for _, attachment := range *jsonItem.Attachments {
+			e := &Enclosure{}
+			e.URL = attachment.URL
+			e.Type = attachment.MimeType
+			e.Length = fmt.Sprintf("%d", attachment.DurationInSeconds)
+			// Title is not defined in global enclosure
+			// SizeInBytes is not defined in global enclosure
+			enclosures = append(enclosures, e)
+		}
+	}
+	return
+}


Test patch:
diff --git a/detector_test.go b/detector_test.go
index 5396c83..8758463 100644
--- a/detector_test.go
+++ b/detector_test.go
@@ -22,6 +22,7 @@ func TestDetectFeedType(t *testing.T) {
 		{"rdf_feed.xml", gofeed.FeedTypeRSS},
 		{"unknown_feed.xml", gofeed.FeedTypeUnknown},
 		{"empty_feed.xml", gofeed.FeedTypeUnknown},
+		{"json_feed.json", gofeed.FeedTypeJSON},
 	}
 
 	for _, test := range feedTypeTests {
diff --git a/json/parser_test.go b/json/parser_test.go
new file mode 100644
index 0000000..f36acbb
--- /dev/null
+++ b/json/parser_test.go
@@ -0,0 +1,78 @@
+package json_test
+
+import (
+	"bytes"
+	"fmt"
+	"io/ioutil"
+	"testing"
+
+	"github.com/mmcdole/gofeed/json"
+	"github.com/stretchr/testify/assert"
+)
+
+// Tests
+
+func TestParser_Parse(t *testing.T) {
+	name := "invalid"
+	fmt.Printf("Testing %s... ", name)
+
+	// Get actual source feed
+	ff := fmt.Sprintf("../testdata/parser/json/%s.json", name)
+	fmt.Println(ff)
+	f, _ := ioutil.ReadFile(ff)
+
+	// Parse actual feed
+	fp := &json.Parser{}
+	_, err := fp.Parse(bytes.NewReader(f))
+	assert.Contains(t, err.Error(), "expect }")
+
+	name = "sample"
+	fmt.Printf("Testing %s... ", name)
+
+	// Get actual source feed
+	ff = fmt.Sprintf("../testdata/parser/json/%s.json", name)
+	fmt.Println(ff)
+	f, _ = ioutil.ReadFile(ff)
+
+	// Parse actual feed
+	actual, _ := fp.Parse(bytes.NewReader(f))
+
+	assert.Equal(t, "1.0", actual.Version)
+	assert.Equal(t, "title", actual.Title)
+	assert.Equal(t, "https://sample-json-feed.com", actual.HomePageURL)
+	assert.Equal(t, "https://sample-json-feed.com/feed.json", actual.FeedURL)
+	assert.Equal(t, "description", actual.Description)
+	assert.Equal(t, "user_comment", actual.UserComment)
+	assert.Equal(t, "https://sample-json-feed.com/feed.json?next=500", actual.NextURL)
+	assert.Equal(t, "https://sample-json-feed.com/icon.png", actual.Icon)
+	assert.Equal(t, "https://sample-json-feed.com/favicon.png", actual.Favicon)
+	assert.Equal(t, "author_name", actual.Author.Name)
+	assert.Equal(t, "https://sample-feed-author.com", actual.Author.URL)
+	assert.Equal(t, "https://sample-feed-author.com/me.png", actual.Author.Avatar)
+	assert.Equal(t, false, actual.Expired)
+	assert.Equal(t, "id", actual.Items[0].ID)
+	assert.Equal(t, "https://sample-json-feed.com/id", actual.Items[0].URL)
+	assert.Equal(t, "https://sample-json-feed.com/external", actual.Items[0].ExternalURL)
+	assert.Equal(t, "title", actual.Items[0].Title)
+	assert.Contains(t, actual.Items[0].ContentHTML, "content_html")
+	assert.Equal(t, "content_text", actual.Items[0].ContentText)
+	assert.Equal(t, "summary", actual.Items[0].Summary)
+	assert.Equal(t, "https://sample-json-feed.com/image.png", actual.Items[0].Image)
+	assert.Equal(t, "https://sample-json-feed.com/banner_image.png", actual.Items[0].BannerImage)
+	assert.Equal(t, "2019-10-12T07:20:50.52Z", actual.Items[0].DatePublished)
+	assert.Equal(t, "2019-10-12T07:20:50.52Z", actual.Items[0].DateModified)
+	assert.Equal(t, "author_name", actual.Items[0].Author.Name)
+	assert.Equal(t, "https://sample-feed-author.com", actual.Items[0].Author.URL)
+	assert.Equal(t, "https://sample-feed-author.com/me.png", actual.Items[0].Author.Avatar)
+	assert.Equal(t, "tag1", actual.Items[0].Tags[0])
+	assert.Equal(t, "tag2", actual.Items[0].Tags[1])
+	assert.Equal(t, "https://sample-json-feed.com/attachment", (*actual.Items[0].Attachments)[0].URL)
+	assert.Equal(t, "audio/mpeg", (*actual.Items[0].Attachments)[0].MimeType)
+	assert.Equal(t, "title", (*actual.Items[0].Attachments)[0].Title)
+	assert.Equal(t, int64(100), (*actual.Items[0].Attachments)[0].SizeInBytes)
+	assert.Equal(t, int64(100), (*actual.Items[0].Attachments)[0].DurationInSeconds)
+
+	assert.Contains(t, actual.String(), "https://sample-json-feed.com/attachment")
+}
+
+// TODO: Examples
diff --git a/parser_test.go b/parser_test.go
index d7d671f..3aa91b4 100644
--- a/parser_test.go
+++ b/parser_test.go
@@ -28,8 +28,10 @@ func TestParser_Parse(t *testing.T) {
 		{"atom10_feed.xml", "atom", "Feed Title", false},
 		{"rss_feed.xml", "rss", "Feed Title", false},
 		{"rdf_feed.xml", "rss", "Feed Title", false},
+		{"sample.json", "json", "title", false},
 		{"unknown_feed.xml", "", "", true},
 		{"empty_feed.xml", "", "", true},
+		{"invalid.json", "", "", true},
 	}
 
 	for _, test := range feedTests {
@@ -66,8 +68,10 @@ func TestParser_ParseString(t *testing.T) {
 		{"atom10_feed.xml", "atom", "Feed Title", false},
 		{"rss_feed.xml", "rss", "Feed Title", false},
 		{"rdf_feed.xml", "rss", "Feed Title", false},
+		{"sample.json", "json", "title", false},
 		{"unknown_feed.xml", "", "", true},
 		{"empty_feed.xml", "", "", true},
+		{"invalid.json", "", "", true},
 	}
 
 	for _, test := range feedTests {
@@ -104,7 +108,9 @@ func TestParser_ParseURL_Success(t *testing.T) {
 		{"atom10_feed.xml", "atom", "Feed Title", false},
 		{"rss_feed.xml", "rss", "Feed Title", false},
 		{"rdf_feed.xml", "rss", "Feed Title", false},
+		{"sample.json", "json", "title", false},
 		{"unknown_feed.xml", "", "", true},
+		{"invalid.json", "", "", true},
 	}
 
 	for _, test := range feedTests {
diff --git a/testdata/parser/json/invalid.json b/testdata/parser/json/invalid.json
new file mode 100644
index 0000000..e188b76
--- /dev/null
+++ b/testdata/parser/json/invalid.json
@@ -0,0 +1,2 @@
+{
+  "verion": "1.0"
diff --git a/testdata/parser/json/sample.json b/testdata/parser/json/sample.json
new file mode 100644
index 0000000..529a927
--- /dev/null
+++ b/testdata/parser/json/sample.json
@@ -0,0 +1,47 @@
+{
+  "version": "1.0",
+  "title": "title",
+  "home_page_url": "https://sample-json-feed.com",
+  "feed_url": "https://sample-json-feed.com/feed.json",
+  "description": "description",
+  "user_comment": "user_comment",
+  "next_url": "https://sample-json-feed.com/feed.json?next=500",
+  "icon": "https://sample-json-feed.com/icon.png",
+  "favicon": "https://sample-json-feed.com/favicon.png",
+  "author": {
+    "name": "author_name",
+    "url": "https://sample-feed-author.com",
+    "avatar": "https://sample-feed-author.com/me.png"
+  },
+  "expired": false,
+  "items": [
+    {
+      "id": "id",
+      "url": "https://sample-json-feed.com/id",
+      "external_url": "https://sample-json-feed.com/external",
+      "title": "title",
+      "content_html": "<p>content_html</p>",
+      "content_text": "content_text",
+      "summary": "summary",
+      "image": "https://sample-json-feed.com/image.png",
+      "banner_image": "https://sample-json-feed.com/banner_image.png",
+      "date_published": "2019-10-12T07:20:50.52Z",
+      "date_modified": "2019-10-12T07:20:50.52Z",
+      "author": {
+        "name": "author_name",
+        "url": "https://sample-feed-author.com",
+        "avatar": "https://sample-feed-author.com/me.png"
+      },
+      "tags": ["tag1", "tag2"],
+      "attachments": [
+        {
+          "url": "https://sample-json-feed.com/attachment",
+          "mime_type": "audio/mpeg",
+          "title": "title",
+          "size_in_bytes": 100,
+          "duration_in_seconds": 100
+        }
+      ]
+    }
+  ]
+}
diff --git a/testdata/parser/universal/invalid.json b/testdata/parser/universal/invalid.json
new file mode 100644
index 0000000..e188b76
--- /dev/null
+++ b/testdata/parser/universal/invalid.json
@@ -0,0 +1,2 @@
+{
+  "verion": "1.0"
diff --git a/testdata/parser/universal/json_feed.json b/testdata/parser/universal/json_feed.json
new file mode 100644
index 0000000..529a927
--- /dev/null
+++ b/testdata/parser/universal/json_feed.json
@@ -0,0 +1,47 @@
+{
+  "version": "1.0",
+  "title": "title",
+  "home_page_url": "https://sample-json-feed.com",
+  "feed_url": "https://sample-json-feed.com/feed.json",
+  "description": "description",
+  "user_comment": "user_comment",
+  "next_url": "https://sample-json-feed.com/feed.json?next=500",
+  "icon": "https://sample-json-feed.com/icon.png",
+  "favicon": "https://sample-json-feed.com/favicon.png",
+  "author": {
+    "name": "author_name",
+    "url": "https://sample-feed-author.com",
+    "avatar": "https://sample-feed-author.com/me.png"
+  },
+  "expired": false,
+  "items": [
+    {
+      "id": "id",
+      "url": "https://sample-json-feed.com/id",
+      "external_url": "https://sample-json-feed.com/external",
+      "title": "title",
+      "content_html": "<p>content_html</p>",
+      "content_text": "content_text",
+      "summary": "summary",
+      "image": "https://sample-json-feed.com/image.png",
+      "banner_image": "https://sample-json-feed.com/banner_image.png",
+      "date_published": "2019-10-12T07:20:50.52Z",
+      "date_modified": "2019-10-12T07:20:50.52Z",
+      "author": {
+        "name": "author_name",
+        "url": "https://sample-feed-author.com",
+        "avatar": "https://sample-feed-author.com/me.png"
+      },
+      "tags": ["tag1", "tag2"],
+      "attachments": [
+        {
+          "url": "https://sample-json-feed.com/attachment",
+          "mime_type": "audio/mpeg",
+          "title": "title",
+          "size_in_bytes": 100,
+          "duration_in_seconds": 100
+        }
+      ]
+    }
+  ]
+}
diff --git a/testdata/parser/universal/sample.json b/testdata/parser/universal/sample.json
new file mode 100644
index 0000000..529a927
--- /dev/null
+++ b/testdata/parser/universal/sample.json
@@ -0,0 +1,47 @@
+{
+  "version": "1.0",
+  "title": "title",
+  "home_page_url": "https://sample-json-feed.com",
+  "feed_url": "https://sample-json-feed.com/feed.json",
+  "description": "description",
+  "user_comment": "user_comment",
+  "next_url": "https://sample-json-feed.com/feed.json?next=500",
+  "icon": "https://sample-json-feed.com/icon.png",
+  "favicon": "https://sample-json-feed.com/favicon.png",
+  "author": {
+    "name": "author_name",
+    "url": "https://sample-feed-author.com",
+    "avatar": "https://sample-feed-author.com/me.png"
+  },
+  "expired": false,
+  "items": [
+    {
+      "id": "id",
+      "url": "https://sample-json-feed.com/id",
+      "external_url": "https://sample-json-feed.com/external",
+      "title": "title",
+      "content_html": "<p>content_html</p>",
+      "content_text": "content_text",
+      "summary": "summary",
+      "image": "https://sample-json-feed.com/image.png",
+      "banner_image": "https://sample-json-feed.com/banner_image.png",
+      "date_published": "2019-10-12T07:20:50.52Z",
+      "date_modified": "2019-10-12T07:20:50.52Z",
+      "author": {
+        "name": "author_name",
+        "url": "https://sample-feed-author.com",
+        "avatar": "https://sample-feed-author.com/me.png"
+      },
+      "tags": ["tag1", "tag2"],
+      "attachments": [
+        {
+          "url": "https://sample-json-feed.com/attachment",
+          "mime_type": "audio/mpeg",
+          "title": "title",
+          "size_in_bytes": 100,
+          "duration_in_seconds": 100
+        }
+      ]
+    }
+  ]
+}
diff --git a/testdata/translator/json/sample.json b/testdata/translator/json/sample.json
new file mode 100644
index 0000000..529a927
--- /dev/null
+++ b/testdata/translator/json/sample.json
@@ -0,0 +1,47 @@
+{
+  "version": "1.0",
+  "title": "title",
+  "home_page_url": "https://sample-json-feed.com",
+  "feed_url": "https://sample-json-feed.com/feed.json",
+  "description": "description",
+  "user_comment": "user_comment",
+  "next_url": "https://sample-json-feed.com/feed.json?next=500",
+  "icon": "https://sample-json-feed.com/icon.png",
+  "favicon": "https://sample-json-feed.com/favicon.png",
+  "author": {
+    "name": "author_name",
+    "url": "https://sample-feed-author.com",
+    "avatar": "https://sample-feed-author.com/me.png"
+  },
+  "expired": false,
+  "items": [
+    {
+      "id": "id",
+      "url": "https://sample-json-feed.com/id",
+      "external_url": "https://sample-json-feed.com/external",
+      "title": "title",
+      "content_html": "<p>content_html</p>",
+      "content_text": "content_text",
+      "summary": "summary",
+      "image": "https://sample-json-feed.com/image.png",
+      "banner_image": "https://sample-json-feed.com/banner_image.png",
+      "date_published": "2019-10-12T07:20:50.52Z",
+      "date_modified": "2019-10-12T07:20:50.52Z",
+      "author": {
+        "name": "author_name",
+        "url": "https://sample-feed-author.com",
+        "avatar": "https://sample-feed-author.com/me.png"
+      },
+      "tags": ["tag1", "tag2"],
+      "attachments": [
+        {
+          "url": "https://sample-json-feed.com/attachment",
+          "mime_type": "audio/mpeg",
+          "title": "title",
+          "size_in_bytes": 100,
+          "duration_in_seconds": 100
+        }
+      ]
+    }
+  ]
+}
diff --git a/testdata/translator/json/sample2.json b/testdata/translator/json/sample2.json
new file mode 100644
index 0000000..1337e24
--- /dev/null
+++ b/testdata/translator/json/sample2.json
@@ -0,0 +1,9 @@
+{
+  "version": "1.0",
+  "items": [
+    {
+      "content_text": "content_text",
+      "banner_image": "https://sample-json-feed.com/banner_image.png"
+    }
+  ]
+}
diff --git a/translator_test.go b/translator_test.go
index a53238d..2cbbb8f 100644
--- a/translator_test.go
+++ b/translator_test.go
@@ -1,16 +1,20 @@
 package gofeed_test
 
 import (
-	"encoding/json"
+	"bytes"
+	jsonEncoding "encoding/json"
 	"fmt"
 	"io/ioutil"
 	"os"
 	"path/filepath"
 	"strings"
 	"testing"
+	"time"
 
 	"github.com/mmcdole/gofeed"
 	"github.com/mmcdole/gofeed/atom"
+	ext "github.com/mmcdole/gofeed/extensions"
+	"github.com/mmcdole/gofeed/json"
 	"github.com/mmcdole/gofeed/rss"
 	"github.com/stretchr/testify/assert"
 )
@@ -40,7 +44,7 @@ func TestDefaultRSSTranslator_Translate(t *testing.T) {
 
 		// Unmarshal expected feed
 		expected := &gofeed.Feed{}
-		json.Unmarshal(e, &expected)
+		jsonEncoding.Unmarshal(e, &expected)
 
 		if assert.Equal(t, actual, expected, "Feed file %s.xml did not match expected output %s.json", name, name) {
 			fmt.Printf("OK\n")
@@ -82,7 +86,7 @@ func TestDefaultAtomTranslator_Translate(t *testing.T) {
 
 		// Unmarshal expected feed
 		expected := &gofeed.Feed{}
-		json.Unmarshal(e, &expected)
+		jsonEncoding.Unmarshal(e, &expected)
 
 		if assert.Equal(t, actual, expected, "Feed file %s.xml did not match expected output %s.json", name, name) {
 			fmt.Printf("OK\n")
@@ -98,3 +102,87 @@ func TestDefaultAtomTranslator_Translate_WrongType(t *testing.T) {
 	assert.Nil(t, af)
 	assert.NotNil(t, err)
 }
+
+func TestDefaultJSONTranslator_Translate(t *testing.T) {
+	name := "sample"
+	fmt.Printf("Testing %s... ", name)
+
+	// Get actual source feed
+	ff := fmt.Sprintf("testdata/translator/json/%s.json", name)
+	fmt.Println(ff)
+	f, _ := ioutil.ReadFile(ff)
+
+	// Parse actual feed
+	translator := &gofeed.DefaultJSONTranslator{}
+	fp := json.Parser{}
+	feed, _ := fp.Parse(bytes.NewReader(f))
+	actual, _ := translator.Translate(feed)
+
+	assert.Equal(t, "title", actual.Title)
+	assert.Equal(t, "description", actual.Description)
+	assert.Equal(t, "https://sample-json-feed.com", actual.Link)
+	assert.Equal(t, "https://sample-json-feed.com/feed.json", actual.FeedLink)
+	assert.Equal(t, "2019-10-12T07:20:50.52Z", actual.Updated)
+	assert.Equal(t, "2019-10-12T07:20:50Z", actual.UpdatedParsed.Format(time.RFC3339))
+	assert.Equal(t, "2019-10-12T07:20:50.52Z", actual.Published)
+	assert.Equal(t, "2019-10-12T07:20:50Z", actual.PublishedParsed.Format(time.RFC3339))
+	assert.Equal(t, "author_name", actual.Author.Name)
+	assert.Equal(t, "", actual.Author.Email)
+	assert.Equal(t, "", actual.Language)
+	assert.Equal(t, "https://sample-json-feed.com/icon.png", actual.Image.URL)
+	assert.Equal(t, "", actual.Image.Title)
+	assert.Equal(t, "", actual.Copyright)
+	assert.Equal(t, "", actual.Generator)
+	assert.Equal(t, 0, len(actual.Categories))
+	assert.Equal(t, (*ext.DublinCoreExtension)(nil), actual.DublinCoreExt)
+	assert.Equal(t, (*ext.ITunesFeedExtension)(nil), actual.ITunesExt)
+	assert.Equal(t, ext.Extensions(nil), actual.Extensions)
+	assert.Equal(t, map[string]string(nil), actual.Custom)
+	assert.Equal(t, "json", actual.FeedType)
+	assert.Equal(t, "1.0", actual.FeedVersion)
+	assert.Equal(t, "title", actual.Items[0].Title)
+	assert.Equal(t, "summary", actual.Items[0].Description)
+	assert.Equal(t, "<p>content_html</p>", actual.Items[0].Content)
+	assert.Equal(t, "https://sample-json-feed.com/id", actual.Items[0].Link)
+	assert.Equal(t, "2019-10-12T07:20:50.52Z", actual.Items[0].Updated)
+	assert.Equal(t, "2019-10-12T07:20:50Z", actual.Items[0].UpdatedParsed.Format(time.RFC3339))
+	assert.Equal(t, "2019-10-12T07:20:50.52Z", actual.Items[0].Published)
+	assert.Equal(t, "2019-10-12T07:20:50Z", actual.Items[0].PublishedParsed.Format(time.RFC3339))
+	assert.Equal(t, "author_name", actual.Items[0].Author.Name)
+	assert.Equal(t, "", actual.Items[0].Author.Email)
+	assert.Equal(t, "id", actual.Items[0].GUID)
+	assert.Equal(t, "https://sample-json-feed.com/image.png", actual.Items[0].Image.URL)
+	assert.Equal(t, "", actual.Items[0].Image.Title)
+	assert.Equal(t, "tag1", actual.Items[0].Categories[0])
+	assert.Equal(t, "tag2", actual.Items[0].Categories[1])
+	assert.Equal(t, "https://sample-json-feed.com/attachment", (actual.Items[0].Enclosures)[0].URL)
+	assert.Equal(t, "100", (actual.Items[0].Enclosures)[0].Length)
+	assert.Equal(t, "audio/mpeg", (actual.Items[0].Enclosures)[0].Type)
+	assert.Equal(t, (*ext.DublinCoreExtension)(nil), actual.Items[0].DublinCoreExt)
+	assert.Equal(t, (*ext.ITunesItemExtension)(nil), actual.Items[0].ITunesExt)
+	assert.Equal(t, ext.Extensions(nil), actual.Items[0].Extensions)
+	assert.Equal(t, map[string]string(nil), actual.Items[0].Custom)
+
+	name = "sample2"
+	fmt.Printf("Testing %s... ", name)
+
+	// Get actual source feed
+	ff = fmt.Sprintf("testdata/translator/json/%s.json", name)
+	fmt.Println(ff)
+	f, _ = ioutil.ReadFile(ff)
+
+	// Parse actual feed
+	feed, _ = fp.Parse(bytes.NewReader(f))
+	actual, _ = translator.Translate(feed)
+
+	assert.Equal(t, "content_text", actual.Items[0].Content)
+	assert.Equal(t, "https://sample-json-feed.com/banner_image.png", actual.Items[0].Image.URL)
+
+}
+
+func TestDefaultJSONTranslator_Translate_WrongType(t *testing.T) {
+	translator := &gofeed.DefaultJSONTranslator{}
+	af, err := translator.Translate("wrong type")
+	assert.Nil(t, af)
+	assert.NotNil(t, err)
+}