diff --git a/Main.hs b/Main.hs
--- a/Main.hs
+++ b/Main.hs
@@ -18,6 +18,7 @@
 import qualified Data.Attoparsec.Lazy as LA hiding (Result, parseOnly)
 import Data.Attoparsec.ByteString.Char8 (endOfLine, sepBy)
 import Data.Attoparsec.Text 
+import qualified Data.Attoparsec.Text as AttoT
 import qualified Data.HashMap.Lazy as HM
 import qualified Data.Vector as V
 import Data.Scientific 
@@ -58,8 +59,9 @@
       chunks :: [Chunk] 
       chunks = parseText template
       results = mconcat $ map (evalText chunks) vs
-  TL.putStrLn . B.toLazyText $ results
+  TL.putStr . B.toLazyText $ results
 
+
 decodeStream :: (FromJSON a) => BL.ByteString -> [a]
 decodeStream bs = case decodeWith json bs of
     (Just x, xs) | xs == mempty -> [x]
@@ -76,18 +78,17 @@
                       _ -> (Nothing, r)) $ fromJSON v'
 
 
-data ArrayFormat = ArrayFormat {
-    arrDelimiter :: Text
-  , arrPrefixStr :: Text  -- can me mempty
-  , arrPostFixStr :: Text
-  } deriving (Show, Eq)
+data ArrayFormat = ArrayFormat Text deriving (Show, Eq)
 
 data Chunk = Pass Text | Expr KeyPath deriving (Show, Eq)
-data KeyPath = KeyPath [Key] ArrayFormat deriving (Show, Eq) -- Text fields are array item prefix and postfix strings, which is given at end
-data Key = Key Text | Index Int deriving (Eq, Show)
+data KeyPath = KeyPath [Key] (Maybe ArrayFormat) 
+      deriving (Show, Eq) 
+data Key = Key Text | Index Int | RootKey deriving (Eq, Show)
 
 evalText :: [Chunk] -> Value -> B.Builder
-evalText xs v = mconcat $ map (B.fromText . evalChunk v) xs
+evalText xs v = 
+    let line = mconcat $ map (B.fromText . evalChunk v) xs
+    in line <> B.fromText "\n"
 
 evalChunk :: Value -> Chunk -> Text
 evalChunk v (Pass s) = s
@@ -101,11 +102,12 @@
 
 -- evaluates the a JS key path against a Value context to a leaf Value
 evalKeyPath :: KeyPath -> Value -> Value
+evalKeyPath (KeyPath [RootKey] _) x = x -- print the root object
 evalKeyPath (KeyPath [] _) x@(String _) = x
 evalKeyPath (KeyPath [] _) x@Null = x
 evalKeyPath (KeyPath [] _) x@(Number _) = x
 evalKeyPath (KeyPath [] _) x@(Bool _) = x
-evalKeyPath (KeyPath [] _) x@(Object _) = x
+evalKeyPath (KeyPath [] _) x@(Object _) = x -- print object
 evalKeyPath (KeyPath (Key key:ks) a) (Object s) = 
     case (HM.lookup key s) of
         Just x          -> evalKeyPath (KeyPath ks a) x
@@ -115,11 +117,14 @@
       in case e of 
         Just e' -> evalKeyPath (KeyPath ks a) e'
         Nothing -> Null  
-evalKeyPath k@(KeyPath _ ArrayFormat {..}) (Array v) = 
+evalKeyPath (KeyPath [] Nothing) x@(Array _) = x  -- print literal object
+evalKeyPath (KeyPath [] (Just (ArrayFormat a))) (Array v) = 
       let vs = V.toList v
-          f =  (\v' -> escapeText $ arrPrefixStr <> evalToUnescapedText k v' <> arrPostFixStr)
-          result = mconcat . intersperse arrDelimiter $ map f vs 
-      in (String result)
+      in String $ mconcat . intersperse a $ map valToUnescapedText $ vs 
+evalKeyPath k@(KeyPath _ a) (Array v) = 
+      let vs = V.toList v
+          f = evalKeyPath k
+      in evalKeyPath (KeyPath [] a) (Array $ V.fromList $ map f vs)
 evalKeyPath (KeyPath (Index _:_) _ ) _ = Null
 evalKeyPath _ _ = Null
 
@@ -140,13 +145,20 @@
     case floatingOrInteger x of
         Left float -> T.pack . show $ float
         Right int -> T.pack . show $ int
-valToText x@(Object _) = error $ "Cannot interpolate " ++ show x
+valToText x@(Object _) = literalJSON x
+valToText x@(Array _) = literalJSON x
 
 escapeStringLiteral :: String -> String
 escapeStringLiteral ('\'':xs) = '\'': ('\'' : escapeStringLiteral xs)
 escapeStringLiteral (x:xs) = x : escapeStringLiteral xs
 escapeStringLiteral [] = []
 
+
+literalJSON :: Value -> Text
+literalJSON x = 
+    let t = T.decodeUtf8 . Prelude.head . BL.toChunks. encode $ x
+    in valToText . String $ t
+
 parseText :: Text -> [Chunk]
 parseText = either error id . parseOnly (many textChunk)
 
@@ -158,7 +170,7 @@
 exprChunk = do
     try (char ':')
     x <- notChar ':'
-    xs <- takeWhile1 identifierChar
+    xs <- AttoT.takeWhile identifierChar
     arrFormat <- pArrayFormat
     let kp = parseKeyPath (T.singleton x <> xs) arrFormat
     return $ Expr kp
@@ -166,30 +178,33 @@
 passChunk :: Parser Chunk
 passChunk = Pass <$> takeWhile1 (notInClass ":")
 
-parseKeyPath :: Text -> ArrayFormat -> KeyPath
+parseKeyPath :: Text -> Maybe ArrayFormat -> KeyPath
 parseKeyPath s a = case parseOnly pKeys s of
     Left err -> error $ "Error parsing key path: " ++ T.unpack s ++ " error: " ++ err 
     Right res -> KeyPath res a
 
 pKeys :: Parser [Key]
 pKeys = do
-    keys <- sepBy1 pKeyOrIndex (takeWhile1 $ inClass ".[") 
+    -- if the first character is '.', then it's an empty keypath
+    -- which evaluates to the whole objject
+    keys <- rootKey <|> (sepBy1 pKeyOrIndex (takeWhile1 $ inClass ".["))
     return keys 
 
--- | syntax is {delimiter-string!prefix-string!postfix-string}
--- immediately after last key
--- e.g. {,!!}
-pArrayFormat :: Parser ArrayFormat
+rootKey :: Parser [Key]
+rootKey = char '.' >> return [RootKey]
+
+-- | syntax is [delimiter-string] immediately after last key
+
+pArrayFormat :: Parser (Maybe ArrayFormat)
 pArrayFormat = do 
+  let leftDelim = '<'
+      rightDelim = '>'
   try (do
-       char '{'
-       delimiter <- T.pack <$> manyTill anyChar (char '!')
-       pre <- T.pack <$> manyTill anyChar (char '!')
-       post <- T.pack <$> manyTill anyChar (char '}')
-       return $ ArrayFormat delimiter pre post)
-  <|> pure defArrayFormat
-
-defArrayFormat = ArrayFormat "," "" ""
+       char leftDelim
+       delimiter <- T.pack <$> manyTill anyChar (char rightDelim)
+       -- char rightDelim
+       return . Just $ ArrayFormat delimiter)
+  <|> pure Nothing
 
 pKeyOrIndex = pIndex <|> pKey
 
diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -63,12 +63,12 @@
 
 ## Array joining
 
-If a key path evaluates to an array of values, the values are converted
-into strings, joined by a delimiter, and then output as a string. The
-default delimiter is a comma:
+If a key path evaluates to an array of values, specify a `<delimiter>` to have
+the the values converted into strings, joined by the delimiter, and then
+output as a string. 
 
 ```
-INSERT into titles (title, year, rating, stars, created) 
+INSERT into titles (title, year, rating, stars<,>, created) 
 VALUES (:title, :year, :ratings.imdb, :stars.name, DEFAULT);
 ```
 
@@ -79,35 +79,29 @@
 VALUES ('Interstellar', 2014, 8.9, 'Matthew McConaughey,Anne Hathaway', DEFAULT);
 ```
 
-A key path that terminates in an array can be followed by an array formatting expression:
-
-```
-{delimiter-string!prefix-sring!postfix-string}
-```
+## JSON literal interpolation
 
+If a key path terminates in a JSON object or array (without a array delimiter
+specified), a JSON literal will be interpolated:
 
 template:
 ```
-INSERT into titles (title, year, rating, stars, created) 
-VALUES (:title, :year, :ratings.imdb, :stars.name{;!$!$}, DEFAULT);
+INSERT into titles (title, stars) 
+VALUES (:title, :stars);
 ```
 
 output:
 ```
-INSERT into titles (title, year, rating, stars, created)
-VALUES ('Terminator 2: ''Judgment Day''', 1991, 8.5, '$Arnold Schwarzenegger$;$Linda Hamilton$', DEFAULT);
-INSERT into titles (title, year, rating, stars, created)
-VALUES ('Interstellar', 2014, 8.9, '$Matthew McConaughey$;$Anne Hathaway$', DEFAULT);
+INSERT into titles (title, stars)
+VALUES ('Terminator 2: ''Judgment Day''', '[{"name":"Arnold Schwarzenegger"},{"name":"Linda Hamilton"}]');
+INSERT into titles (title, stars) 
+VALUES ('Interstellar', '[{"name":"Matthew McConaughey"},{"name":"Anne Hathaway"}]');
 ```
 
-Adding a prefix and postfix may be useful if you want to mark strings
-for downstream pipeline processing with tools like `sed` before reaching
-the database.
+To interpolate the base object as a literal JSON string, use `:.` as the keypath.
 
-The usefulness of this feature may be obscure. But the author needed it to
-change an array of strings like `["apple","banana","pear"]` into a string field
-containing a series of integer IDs like `'1,2,3'`. This type of field was then 
-indexed by the Sphinx search engine in a multi-valued attribute.
+Do NOT put quotes around the placeholder for a literal JSON interpolation.
+
 
 ## Author
 
diff --git a/jsonsql.cabal b/jsonsql.cabal
--- a/jsonsql.cabal
+++ b/jsonsql.cabal
@@ -2,7 +2,7 @@
 -- documentation, see http://haskell.org/cabal/users-guide/
 
 name:                jsonsql
-version:             0.1.0.1
+version:             0.1.2.0
 synopsis:            Interpolate JSON object values into SQL strings
 -- description:         
 homepage:            https://github.com/danchoi/jsonsql
diff --git a/stack.yaml b/stack.yaml
new file mode 100644
--- /dev/null
+++ b/stack.yaml
@@ -0,0 +1,6 @@
+flags: {}
+packages:
+- '.'
+extra-deps: 
+- string-qq-0.0.2
+resolver: lts-2.16
diff --git a/test.json b/test.json
new file mode 100644
--- /dev/null
+++ b/test.json
@@ -0,0 +1,24 @@
+{
+  "title": "Terminator 2: 'Judgment Day'",
+  "year": 1991,
+  "stars": [
+    {"name": "Arnold Schwarzenegger"},
+    {"name": "Linda Hamilton"}
+  ],
+  "ratings": {
+    "imdb": 8.5
+  },
+  "created": "2014-12-04T10:10:10Z"
+  
+}
+{
+  "title": "Interstellar",
+  "year": 2014,
+  "stars": [
+    {"name":"Matthew McConaughey"},
+    {"name":"Anne Hathaway"}
+  ],
+  "ratings": {
+    "imdb": 8.9
+  }
+}
