diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,12 @@
+# Changelog
+
+`vflow-types` uses [PVP Versioning][1].
+The changelog is available [on GitHub][2].
+
+0.0.0
+=====
+
+* Initially created.
+
+[1]: https://pvp.haskell.org
+[2]: https://github.com/chessai/vflow-types/releases
diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,29 @@
+BSD 3-Clause License
+
+Copyright (c) 2019, chessai
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice, this
+   list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice,
+   this list of conditions and the following disclaimer in the documentation
+   and/or other materials provided with the distribution.
+
+3. Neither the name of the copyright holder nor the names of its
+   contributors may be used to endorse or promote products derived from
+   this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/README.md b/README.md
new file mode 100644
--- /dev/null
+++ b/README.md
@@ -0,0 +1,6 @@
+# vflow-types
+
+[![Hackage](https://img.shields.io/hackage/v/vflow-types.svg)](https://hackage.haskell.org/package/vflow-types)
+[![BSD3 license](https://img.shields.io/badge/license-BSD3-blue.svg)](LICENSE)
+
+types for ingesting [vflow](https://github.com/VerizonDigital/vflow)-generated data. All types have ToJSON/FromJSON instances. `vflow-types` can handle IPFIX, sFlow, Netflow v5, and Netflow v9.
diff --git a/src/VFlow/Types/IpFix.hs b/src/VFlow/Types/IpFix.hs
new file mode 100644
--- /dev/null
+++ b/src/VFlow/Types/IpFix.hs
@@ -0,0 +1,93 @@
+{-# LANGUAGE BangPatterns        #-}
+{-# LANGUAGE DeriveGeneric       #-}
+{-# LANGUAGE OverloadedStrings   #-}
+{-# LANGUAGE RecordWildCards     #-}
+{-# LANGUAGE TypeOperators       #-}
+
+module VFlow.Types.IpFix
+  ( IpFix(..)
+  , DataSetsEltElt(..)
+  , Header(..)
+  , HexInt(..)
+  )
+  where
+
+import Control.Monad (mzero)
+import Data.Aeson(Value(..), FromJSON(..), ToJSON(..), pairs, (.:), (.=), object)
+import Data.Aeson.AutoType.Alternative
+import Data.Int (Int64)
+import Data.Monoid((<>))
+import Net.IPv4 (IPv4)
+import Text.Read (readMaybe)
+import qualified Data.Scientific as Sci
+import qualified Data.Text
+import qualified GHC.Generics
+
+newtype HexInt = HexInt Int64
+  deriving (Eq, Show)
+
+instance FromJSON HexInt where
+  parseJSON (String s) = case readMaybe (Data.Text.unpack s) of
+    Nothing -> mzero
+    Just x -> pure (HexInt x)
+  parseJSON (Number s) = if Sci.isInteger s
+    then case Sci.toBoundedInteger s of
+      Nothing -> mzero
+      Just x -> pure (HexInt x)
+    else mzero
+  parseJSON _ = mzero
+
+instance ToJSON HexInt where
+  toJSON (HexInt x) = toJSON x
+  toEncoding (HexInt x) = toEncoding x
+
+data DataSetsEltElt = DataSetsEltElt { 
+    dataSetsEltEltV :: !(IPv4 :|: HexInt),
+    dataSetsEltEltI :: !Int
+  } deriving (Show,Eq,GHC.Generics.Generic)
+
+
+instance FromJSON DataSetsEltElt where
+  parseJSON (Object v) = DataSetsEltElt <$> v .:  "V" <*> v .:  "I"
+  parseJSON _          = mzero
+
+
+instance ToJSON DataSetsEltElt where
+  toJSON     (DataSetsEltElt {..}) = object ["V" .= dataSetsEltEltV, "I" .= dataSetsEltEltI]
+  toEncoding (DataSetsEltElt {..}) = pairs  ("V" .= dataSetsEltEltV<>"I" .= dataSetsEltEltI)
+
+
+data Header = Header { 
+    headerLength :: !Int,
+    headerSequenceNo :: !Int,
+    headerExportTime :: !Int,
+    headerVersion :: !Int,
+    headerDomainID :: !Int
+  } deriving (Show,Eq,GHC.Generics.Generic)
+
+
+instance FromJSON Header where
+  parseJSON (Object v) = Header <$> v .:  "Length" <*> v .:  "SequenceNo" <*> v .:  "ExportTime" <*> v .:  "Version" <*> v .:  "DomainID"
+  parseJSON _          = mzero
+
+
+instance ToJSON Header where
+  toJSON     (Header {..}) = object ["Length" .= headerLength, "SequenceNo" .= headerSequenceNo, "ExportTime" .= headerExportTime, "Version" .= headerVersion, "DomainID" .= headerDomainID]
+  toEncoding (Header {..}) = pairs  ("Length" .= headerLength<>"SequenceNo" .= headerSequenceNo<>"ExportTime" .= headerExportTime<>"Version" .= headerVersion<>"DomainID" .= headerDomainID)
+
+
+data IpFix = IpFix { 
+    ipFixAgentID :: !IPv4,
+    ipFixHeader :: !Header,
+    ipFixDataSets :: [[DataSetsEltElt]]
+  } deriving (Show,Eq,GHC.Generics.Generic)
+
+
+instance FromJSON IpFix where
+  parseJSON (Object v) = IpFix <$> v .:  "AgentID" <*> v .:  "Header" <*> v .:  "DataSets"
+  parseJSON _          = mzero
+
+
+instance ToJSON IpFix where
+  toJSON     (IpFix {..}) = object ["AgentID" .= ipFixAgentID, "Header" .= ipFixHeader, "DataSets" .= ipFixDataSets]
+  toEncoding (IpFix {..}) = pairs  ("AgentID" .= ipFixAgentID<>"Header" .= ipFixHeader<>"DataSets" .= ipFixDataSets)
diff --git a/src/VFlow/Types/NetFlow5.hs b/src/VFlow/Types/NetFlow5.hs
new file mode 100644
--- /dev/null
+++ b/src/VFlow/Types/NetFlow5.hs
@@ -0,0 +1,91 @@
+{-# LANGUAGE BangPatterns        #-}
+{-# LANGUAGE RecordWildCards     #-}
+{-# LANGUAGE OverloadedStrings   #-}
+{-# LANGUAGE TypeOperators       #-}
+{-# LANGUAGE DeriveGeneric       #-}
+
+module VFlow.Types.NetFlow5
+  ( NetFlow(..)
+  , FlowsElt(..)
+  , Header(..)
+  ) where
+
+import Control.Monad (mzero)
+import Data.Aeson(Value(..), FromJSON(..), ToJSON(..), pairs, (.:), (.=), object)
+import Data.Monoid((<>))
+import qualified GHC.Generics
+import Net.Types (IPv4)
+
+data FlowsElt = FlowsElt { 
+    flowsEltDstAsNum :: !Int,
+    flowsEltStartTime :: !Int,
+    flowsEltL3Octets :: !Int,
+    flowsEltTCPFlags :: !Int,
+    flowsEltDstAddr :: !IPv4,
+    flowsEltTos :: !Int,
+    flowsEltPadding1 :: !Int,
+    flowsEltNextHop :: !IPv4,
+    flowsEltSrcPort :: !Int,
+    flowsEltInput :: !Int,
+    flowsEltPktCount :: !Int,
+    flowsEltOutput :: !Int,
+    flowsEltDstMask :: !Int,
+    flowsEltDstPort :: !Int,
+    flowsEltEndTime :: !Int,
+    flowsEltPadding2 :: !Int,
+    flowsEltSrcMask :: !Int,
+    flowsEltSrcAsNum :: !Int,
+    flowsEltSrcAddr :: !IPv4,
+    flowsEltProtType :: !Int
+  } deriving (Show,Eq,GHC.Generics.Generic)
+
+
+instance FromJSON FlowsElt where
+  parseJSON (Object v) = FlowsElt <$> v .:  "DstAsNum" <*> v .:  "StartTime" <*> v .:  "L3Octets" <*> v .:  "TCPFlags" <*> v .:  "DstAddr" <*> v .:  "Tos" <*> v .:  "Padding1" <*> v .:  "NextHop" <*> v .:  "SrcPort" <*> v .:  "Input" <*> v .:  "PktCount" <*> v .:  "Output" <*> v .:  "DstMask" <*> v .:  "DstPort" <*> v .:  "EndTime" <*> v .:  "Padding2" <*> v .:  "SrcMask" <*> v .:  "SrcAsNum" <*> v .:  "SrcAddr" <*> v .:  "ProtType"
+  parseJSON _          = mzero
+
+
+instance ToJSON FlowsElt where
+  toJSON     (FlowsElt {..}) = object ["DstAsNum" .= flowsEltDstAsNum, "StartTime" .= flowsEltStartTime, "L3Octets" .= flowsEltL3Octets, "TCPFlags" .= flowsEltTCPFlags, "DstAddr" .= flowsEltDstAddr, "Tos" .= flowsEltTos, "Padding1" .= flowsEltPadding1, "NextHop" .= flowsEltNextHop, "SrcPort" .= flowsEltSrcPort, "Input" .= flowsEltInput, "PktCount" .= flowsEltPktCount, "Output" .= flowsEltOutput, "DstMask" .= flowsEltDstMask, "DstPort" .= flowsEltDstPort, "EndTime" .= flowsEltEndTime, "Padding2" .= flowsEltPadding2, "SrcMask" .= flowsEltSrcMask, "SrcAsNum" .= flowsEltSrcAsNum, "SrcAddr" .= flowsEltSrcAddr, "ProtType" .= flowsEltProtType]
+  toEncoding (FlowsElt {..}) = pairs  ("DstAsNum" .= flowsEltDstAsNum<>"StartTime" .= flowsEltStartTime<>"L3Octets" .= flowsEltL3Octets<>"TCPFlags" .= flowsEltTCPFlags<>"DstAddr" .= flowsEltDstAddr<>"Tos" .= flowsEltTos<>"Padding1" .= flowsEltPadding1<>"NextHop" .= flowsEltNextHop<>"SrcPort" .= flowsEltSrcPort<>"Input" .= flowsEltInput<>"PktCount" .= flowsEltPktCount<>"Output" .= flowsEltOutput<>"DstMask" .= flowsEltDstMask<>"DstPort" .= flowsEltDstPort<>"EndTime" .= flowsEltEndTime<>"Padding2" .= flowsEltPadding2<>"SrcMask" .= flowsEltSrcMask<>"SrcAsNum" .= flowsEltSrcAsNum<>"SrcAddr" .= flowsEltSrcAddr<>"ProtType" .= flowsEltProtType)
+
+
+data Header = Header { 
+    headerUNIXSecs :: !Int,
+    headerCount :: !Int,
+    headerEngType :: !Int,
+    headerVersion :: !Int,
+    headerSysUpTimeMSecs :: !Int,
+    headerUNIXNSecs :: !Int,
+    headerSmpInt :: !Int,
+    headerSeqNum :: !Int,
+    headerEngID :: !Int
+  } deriving (Show,Eq,GHC.Generics.Generic)
+
+
+instance FromJSON Header where
+  parseJSON (Object v) = Header <$> v .:  "UNIXSecs" <*> v .:  "Count" <*> v .:  "EngType" <*> v .:  "Version" <*> v .:  "SysUpTimeMSecs" <*> v .:  "UNIXNSecs" <*> v .:  "SmpInt" <*> v .:  "SeqNum" <*> v .:  "EngID"
+  parseJSON _          = mzero
+
+
+instance ToJSON Header where
+  toJSON     (Header {..}) = object ["UNIXSecs" .= headerUNIXSecs, "Count" .= headerCount, "EngType" .= headerEngType, "Version" .= headerVersion, "SysUpTimeMSecs" .= headerSysUpTimeMSecs, "UNIXNSecs" .= headerUNIXNSecs, "SmpInt" .= headerSmpInt, "SeqNum" .= headerSeqNum, "EngID" .= headerEngID]
+  toEncoding (Header {..}) = pairs  ("UNIXSecs" .= headerUNIXSecs<>"Count" .= headerCount<>"EngType" .= headerEngType<>"Version" .= headerVersion<>"SysUpTimeMSecs" .= headerSysUpTimeMSecs<>"UNIXNSecs" .= headerUNIXNSecs<>"SmpInt" .= headerSmpInt<>"SeqNum" .= headerSeqNum<>"EngID" .= headerEngID)
+
+
+data NetFlow = NetFlow { 
+    netflowAgentID :: !IPv4,
+    netflowHeader :: !Header,
+    netflowFlows :: [FlowsElt]
+  } deriving (Show,Eq,GHC.Generics.Generic)
+
+
+instance FromJSON NetFlow where
+  parseJSON (Object v) = NetFlow <$> v .:  "AgentID" <*> v .:  "Header" <*> v .:  "Flows"
+  parseJSON _          = mzero
+
+
+instance ToJSON NetFlow where
+  toJSON     (NetFlow {..}) = object ["AgentID" .= netflowAgentID, "Header" .= netflowHeader, "Flows" .= netflowFlows]
+  toEncoding (NetFlow {..}) = pairs  ("AgentID" .= netflowAgentID<>"Header" .= netflowHeader<>"Flows" .= netflowFlows)
+
diff --git a/src/VFlow/Types/NetFlow9.hs b/src/VFlow/Types/NetFlow9.hs
new file mode 100644
--- /dev/null
+++ b/src/VFlow/Types/NetFlow9.hs
@@ -0,0 +1,91 @@
+{-# LANGUAGE BangPatterns        #-}
+{-# LANGUAGE DeriveGeneric       #-}
+{-# LANGUAGE OverloadedStrings   #-}
+{-# LANGUAGE RecordWildCards     #-}
+{-# LANGUAGE TypeOperators       #-}
+
+module VFlow.Types.NetFlow9
+  ( NetFlow(..)
+  , Header(..)
+  , DataSetsEltElt(..)
+  , HexInt(..)
+  ) where
+
+import Control.Monad (mzero)
+import Data.Aeson(Value(..), FromJSON(..), ToJSON(..), pairs, (.:), (.=), object)
+import Data.Aeson.AutoType.Alternative
+import Data.Int (Int64)
+import Data.Monoid((<>))
+import Net.Types (IPv4)
+import Text.Read (readMaybe)
+import qualified Data.Scientific as Sci
+import qualified Data.Text
+import qualified GHC.Generics
+
+newtype HexInt = HexInt Int64
+  deriving (Eq, Show)
+
+instance FromJSON HexInt where
+  parseJSON (String s) = case readMaybe (Data.Text.unpack s) of
+    Nothing -> mzero
+    Just x -> pure (HexInt x)
+  parseJSON (Number s) = if Sci.isInteger s
+    then case Sci.toBoundedInteger s of
+      Nothing -> mzero
+      Just x -> pure (HexInt x)
+    else mzero
+  parseJSON _ = mzero
+
+instance ToJSON HexInt where
+  toJSON (HexInt x) = toJSON x
+  toEncoding (HexInt x) = toEncoding x
+
+data DataSetsEltElt = DataSetsEltElt { 
+    dataSetsEltEltV :: !(IPv4 :|: HexInt),
+    dataSetsEltEltI :: !Int
+  } deriving (Show,Eq,GHC.Generics.Generic)
+
+instance FromJSON DataSetsEltElt where
+  parseJSON (Object v) = DataSetsEltElt <$> v .:  "V" <*> v .:  "I"
+  parseJSON _          = mzero
+
+instance ToJSON DataSetsEltElt where
+  toJSON     (DataSetsEltElt {..}) = object ["V" .= dataSetsEltEltV, "I" .= dataSetsEltEltI]
+  toEncoding (DataSetsEltElt {..}) = pairs  ("V" .= dataSetsEltEltV<>"I" .= dataSetsEltEltI)
+
+
+data Header = Header { 
+    headerUNIXSecs :: !Int,
+    headerSrcID :: !Int,
+    headerCount :: !Int,
+    headerSysUpTime :: !Int,
+    headerVersion :: !Int,
+    headerSeqNum :: !Int
+  } deriving (Show,Eq,GHC.Generics.Generic)
+
+
+instance FromJSON Header where
+  parseJSON (Object v) = Header <$> v .:  "UNIXSecs" <*> v .:  "SrcID" <*> v .:  "Count" <*> v .:  "SysUpTime" <*> v .:  "Version" <*> v .:  "SeqNum"
+  parseJSON _          = mzero
+
+
+instance ToJSON Header where
+  toJSON     (Header {..}) = object ["UNIXSecs" .= headerUNIXSecs, "SrcID" .= headerSrcID, "Count" .= headerCount, "SysUpTime" .= headerSysUpTime, "Version" .= headerVersion, "SeqNum" .= headerSeqNum]
+  toEncoding (Header {..}) = pairs  ("UNIXSecs" .= headerUNIXSecs<>"SrcID" .= headerSrcID<>"Count" .= headerCount<>"SysUpTime" .= headerSysUpTime<>"Version" .= headerVersion<>"SeqNum" .= headerSeqNum)
+
+
+data NetFlow = NetFlow { 
+    netflowAgentID :: !IPv4,
+    netflowHeader :: !Header,
+    netflowDataSets :: [[DataSetsEltElt]]
+  } deriving (Show,Eq,GHC.Generics.Generic)
+
+
+instance FromJSON NetFlow where
+  parseJSON (Object v) = NetFlow <$> v .:  "AgentID" <*> v .:  "Header" <*> v .:  "DataSets"
+  parseJSON _          = mzero
+
+
+instance ToJSON NetFlow where
+  toJSON     (NetFlow {..}) = object ["AgentID" .= netflowAgentID, "Header" .= netflowHeader, "DataSets" .= netflowDataSets]
+  toEncoding (NetFlow {..}) = pairs  ("AgentID" .= netflowAgentID<>"Header" .= netflowHeader<>"DataSets" .= netflowDataSets)
diff --git a/src/VFlow/Types/SFlow.hs b/src/VFlow/Types/SFlow.hs
new file mode 100644
--- /dev/null
+++ b/src/VFlow/Types/SFlow.hs
@@ -0,0 +1,198 @@
+{-# LANGUAGE BangPatterns        #-}
+{-# LANGUAGE DeriveGeneric       #-}
+{-# LANGUAGE OverloadedStrings   #-}
+{-# LANGUAGE RecordWildCards     #-}
+{-# LANGUAGE TypeOperators       #-}
+
+module VFlow.Types.SFlow
+  ( SFlow(..)
+  , ExtRouter(..)
+  , ExtSwitch(..)
+  , L2(..)
+  , L3(..)
+  , L4(..)
+  , RawHeader(..)
+  , Records(..)
+  , SamplesElt(..)
+  ) where
+
+import Control.Monad (mzero)
+import Data.Aeson (Value(..), FromJSON(..), ToJSON(..), pairs, (.:), (.=), object)
+import Net.Types (IPv4, Mac)
+import qualified GHC.Generics
+
+data ExtRouter = ExtRouter { 
+    extRouterNextHop :: !IPv4,
+    extRouterDstMask :: !Int,
+    extRouterSrcMask :: !Int
+  } deriving (Show,Eq,GHC.Generics.Generic)
+
+
+instance FromJSON ExtRouter where
+  parseJSON (Object v) = ExtRouter <$> v .:  "NextHop" <*> v .:  "DstMask" <*> v .:  "SrcMask"
+  parseJSON _          = mzero
+
+
+instance ToJSON ExtRouter where
+  toJSON     (ExtRouter {..}) = object ["NextHop" .= extRouterNextHop, "DstMask" .= extRouterDstMask, "SrcMask" .= extRouterSrcMask]
+  toEncoding (ExtRouter {..}) = pairs  ("NextHop" .= extRouterNextHop<>"DstMask" .= extRouterDstMask<>"SrcMask" .= extRouterSrcMask)
+
+
+data ExtSwitch = ExtSwitch { 
+    extSwitchDstPriority :: !Int,
+    extSwitchSrcVlan :: !Int,
+    extSwitchSrcPriority :: !Int,
+    extSwitchDstVlan :: !Int
+  } deriving (Show,Eq,GHC.Generics.Generic)
+
+
+instance FromJSON ExtSwitch where
+  parseJSON (Object v) = ExtSwitch <$> v .:  "DstPriority" <*> v .:  "SrcVlan" <*> v .:  "SrcPriority" <*> v .:  "DstVlan"
+  parseJSON _          = mzero
+
+
+instance ToJSON ExtSwitch where
+  toJSON     (ExtSwitch {..}) = object ["DstPriority" .= extSwitchDstPriority, "SrcVlan" .= extSwitchSrcVlan, "SrcPriority" .= extSwitchSrcPriority, "DstVlan" .= extSwitchDstVlan]
+  toEncoding (ExtSwitch {..}) = pairs  ("DstPriority" .= extSwitchDstPriority<>"SrcVlan" .= extSwitchSrcVlan<>"SrcPriority" .= extSwitchSrcPriority<>"DstVlan" .= extSwitchDstVlan)
+
+
+data L2 = L2 { 
+    l2Vlan :: !Int,
+    l2EtherType :: !Int,
+    l2DstMAC :: !Mac,
+    l2SrcMAC :: !Mac
+  } deriving (Show,Eq,GHC.Generics.Generic)
+
+
+instance FromJSON L2 where
+  parseJSON (Object v) = L2 <$> v .:  "Vlan" <*> v .:  "EtherType" <*> v .:  "DstMAC" <*> v .:  "SrcMAC"
+  parseJSON _          = mzero
+
+
+instance ToJSON L2 where
+  toJSON     (L2 {..}) = object ["Vlan" .= l2Vlan, "EtherType" .= l2EtherType, "DstMAC" .= l2DstMAC, "SrcMAC" .= l2SrcMAC]
+  toEncoding (L2 {..}) = pairs  ("Vlan" .= l2Vlan<>"EtherType" .= l2EtherType<>"DstMAC" .= l2DstMAC<>"SrcMAC" .= l2SrcMAC)
+
+
+data L3 = L3 { 
+    l3TTL :: !Int,
+    l3Flags :: !Int,
+    l3TotalLen :: !Int,
+    l3Checksum :: !Int,
+    l3TOS :: !Int,
+    l3Dst :: !IPv4,
+    l3Protocol :: !Int,
+    l3Src :: !IPv4,
+    l3Version :: !Int,
+    l3ID :: !Int,
+    l3FragOff :: !Int
+  } deriving (Show,Eq,GHC.Generics.Generic)
+
+
+instance FromJSON L3 where
+  parseJSON (Object v) = L3 <$> v .:  "TTL" <*> v .:  "Flags" <*> v .:  "TotalLen" <*> v .:  "Checksum" <*> v .:  "TOS" <*> v .:  "Dst" <*> v .:  "Protocol" <*> v .:  "Src" <*> v .:  "Version" <*> v .:  "ID" <*> v .:  "FragOff"
+  parseJSON _          = mzero
+
+
+instance ToJSON L3 where
+  toJSON     (L3 {..}) = object ["TTL" .= l3TTL, "Flags" .= l3Flags, "TotalLen" .= l3TotalLen, "Checksum" .= l3Checksum, "TOS" .= l3TOS, "Dst" .= l3Dst, "Protocol" .= l3Protocol, "Src" .= l3Src, "Version" .= l3Version, "ID" .= l3ID, "FragOff" .= l3FragOff]
+  toEncoding (L3 {..}) = pairs  ("TTL" .= l3TTL<>"Flags" .= l3Flags<>"TotalLen" .= l3TotalLen<>"Checksum" .= l3Checksum<>"TOS" .= l3TOS<>"Dst" .= l3Dst<>"Protocol" .= l3Protocol<>"Src" .= l3Src<>"Version" .= l3Version<>"ID" .= l3ID<>"FragOff" .= l3FragOff)
+
+
+data L4 = L4 { 
+    l4Flags :: !Int,
+    l4DataOffset :: !Int,
+    l4SrcPort :: !Int,
+    l4Reserved :: !Int,
+    l4DstPort :: !Int
+  } deriving (Show,Eq,GHC.Generics.Generic)
+
+
+instance FromJSON L4 where
+  parseJSON (Object v) = L4 <$> v .:  "Flags" <*> v .:  "DataOffset" <*> v .:  "SrcPort" <*> v .:  "Reserved" <*> v .:  "DstPort"
+  parseJSON _          = mzero
+
+
+instance ToJSON L4 where
+  toJSON     (L4 {..}) = object ["Flags" .= l4Flags, "DataOffset" .= l4DataOffset, "SrcPort" .= l4SrcPort, "Reserved" .= l4Reserved, "DstPort" .= l4DstPort]
+  toEncoding (L4 {..}) = pairs  ("Flags" .= l4Flags<>"DataOffset" .= l4DataOffset<>"SrcPort" .= l4SrcPort<>"Reserved" .= l4Reserved<>"DstPort" .= l4DstPort)
+
+
+data RawHeader = RawHeader { 
+    rawHeaderL2 :: !L2,
+    rawHeaderL3 :: !L3,
+    rawHeaderL4 :: !L4
+  } deriving (Show,Eq,GHC.Generics.Generic)
+
+
+instance FromJSON RawHeader where
+  parseJSON (Object v) = RawHeader <$> v .:  "L2" <*> v .:  "L3" <*> v .:  "L4"
+  parseJSON _          = mzero
+
+
+instance ToJSON RawHeader where
+  toJSON     (RawHeader {..}) = object ["L2" .= rawHeaderL2, "L3" .= rawHeaderL3, "L4" .= rawHeaderL4]
+  toEncoding (RawHeader {..}) = pairs  ("L2" .= rawHeaderL2<>"L3" .= rawHeaderL3<>"L4" .= rawHeaderL4)
+
+
+data Records = Records { 
+    recordsExtRouter :: !ExtRouter,
+    recordsExtSwitch :: !ExtSwitch,
+    recordsRawHeader :: !RawHeader
+  } deriving (Show,Eq,GHC.Generics.Generic)
+
+
+instance FromJSON Records where
+  parseJSON (Object v) = Records <$> v .:  "ExtRouter" <*> v .:  "ExtSwitch" <*> v .:  "RawHeader"
+  parseJSON _          = mzero
+
+
+instance ToJSON Records where
+  toJSON     (Records {..}) = object ["ExtRouter" .= recordsExtRouter, "ExtSwitch" .= recordsExtSwitch, "RawHeader" .= recordsRawHeader]
+  toEncoding (Records {..}) = pairs  ("ExtRouter" .= recordsExtRouter<>"ExtSwitch" .= recordsExtSwitch<>"RawHeader" .= recordsRawHeader)
+
+
+data SamplesElt = SamplesElt { 
+    samplesEltDrops :: !Int,
+    samplesEltSourceID :: !Int,
+    samplesEltRecords :: !Records,
+    samplesEltInput :: !Int,
+    samplesEltSequenceNo :: !Int,
+    samplesEltSamplingRate :: !Int,
+    samplesEltOutput :: !Int,
+    samplesEltRecordsNo :: !Int,
+    samplesEltSamplePool :: !Int
+  } deriving (Show,Eq,GHC.Generics.Generic)
+
+
+instance FromJSON SamplesElt where
+  parseJSON (Object v) = SamplesElt <$> v .:  "Drops" <*> v .:  "SourceID" <*> v .:  "Records" <*> v .:  "Input" <*> v .:  "SequenceNo" <*> v .:  "SamplingRate" <*> v .:  "Output" <*> v .:  "RecordsNo" <*> v .:  "SamplePool"
+  parseJSON _          = mzero
+
+
+instance ToJSON SamplesElt where
+  toJSON     (SamplesElt {..}) = object ["Drops" .= samplesEltDrops, "SourceID" .= samplesEltSourceID, "Records" .= samplesEltRecords, "Input" .= samplesEltInput, "SequenceNo" .= samplesEltSequenceNo, "SamplingRate" .= samplesEltSamplingRate, "Output" .= samplesEltOutput, "RecordsNo" .= samplesEltRecordsNo, "SamplePool" .= samplesEltSamplePool]
+  toEncoding (SamplesElt {..}) = pairs  ("Drops" .= samplesEltDrops<>"SourceID" .= samplesEltSourceID<>"Records" .= samplesEltRecords<>"Input" .= samplesEltInput<>"SequenceNo" .= samplesEltSequenceNo<>"SamplingRate" .= samplesEltSamplingRate<>"Output" .= samplesEltOutput<>"RecordsNo" .= samplesEltRecordsNo<>"SamplePool" .= samplesEltSamplePool)
+
+
+data SFlow = SFlow { 
+    sflowIPAddress :: !IPv4,
+    sflowAgentSubID :: !Int,
+    sflowIPVersion :: !Int,
+    sflowSequenceNo :: !Int,
+    sflowSysUpTime :: !Int,
+    sflowSamplesNo :: !Int,
+    sflowVersion :: !Int,
+    sflowSamples :: [SamplesElt]
+  } deriving (Show,Eq,GHC.Generics.Generic)
+
+
+instance FromJSON SFlow where
+  parseJSON (Object v) = SFlow <$> v .:  "IPAddress" <*> v .:  "AgentSubID" <*> v .:  "IPVersion" <*> v .:  "SequenceNo" <*> v .:  "SysUpTime" <*> v .:  "SamplesNo" <*> v .:  "Version" <*> v .:  "Samples"
+  parseJSON _          = mzero
+
+
+instance ToJSON SFlow where
+  toJSON     (SFlow {..}) = object ["IPAddress" .= sflowIPAddress, "AgentSubID" .= sflowAgentSubID, "IPVersion" .= sflowIPVersion, "SequenceNo" .= sflowSequenceNo, "SysUpTime" .= sflowSysUpTime, "SamplesNo" .= sflowSamplesNo, "Version" .= sflowVersion, "Samples" .= sflowSamples]
+  toEncoding (SFlow {..}) = pairs  ("IPAddress" .= sflowIPAddress<>"AgentSubID" .= sflowAgentSubID<>"IPVersion" .= sflowIPVersion<>"SequenceNo" .= sflowSequenceNo<>"SysUpTime" .= sflowSysUpTime<>"SamplesNo" .= sflowSamplesNo<>"Version" .= sflowVersion<>"Samples" .= sflowSamples)
+
diff --git a/test/Laws.hs b/test/Laws.hs
new file mode 100644
--- /dev/null
+++ b/test/Laws.hs
@@ -0,0 +1,101 @@
+{-# language TypeOperators #-}
+
+module Main (main) where
+
+import Data.Aeson.AutoType.Alternative
+import Data.Proxy (Proxy(..))
+import Data.Word (Word8)
+import Net.Types (IPv4,Mac)
+import Test.QuickCheck
+import Test.QuickCheck.Classes
+import VFlow.Types.IpFix (IpFix(..))
+import VFlow.Types.SFlow (SFlow(..))
+import qualified Net.IPv4 as I4
+import qualified Net.Mac as Mac
+import qualified VFlow.Types.IpFix as I
+import qualified VFlow.Types.NetFlow5 as N5
+import qualified VFlow.Types.NetFlow9 as N9
+import qualified VFlow.Types.SFlow as S
+
+main :: IO ()
+main = lawsCheckMany laws
+
+laws :: [(String,[Laws])]
+laws =
+  [ ("IPFIX", [jsonLaws (Proxy :: Proxy IpFix)])
+  , ("sflow", [jsonLaws (Proxy :: Proxy SFlow)])
+  , ("Netflow v5", [jsonLaws (Proxy :: Proxy N5.NetFlow)])
+  , ("Netflow v9", [jsonLaws (Proxy :: Proxy N9.NetFlow)])
+  ]
+
+instance (Arbitrary a, Arbitrary b) => Arbitrary (a :|: b) where
+  arbitrary = frequency [ (1, AltLeft <$> arbitrary), (1, AltRight <$> arbitrary) ]
+
+instance Arbitrary IpFix where
+  arbitrary = IpFix <$> arbitrary <*> arbitrary <*> arbitrary
+
+instance Arbitrary I.Header where
+  arbitrary = I.Header <$> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary
+
+instance Arbitrary I.DataSetsEltElt where
+  arbitrary = I.DataSetsEltElt <$> arbitrary <*> arbitrary
+
+instance Arbitrary IPv4 where
+  arbitrary = I4.ipv4 <$> word8 <*> word8 <*> word8 <*> word8
+
+instance Arbitrary Mac where
+  arbitrary = Mac.fromOctets <$> word8 <*> word8 <*> word8 <*> word8 <*> word8 <*> word8
+ 
+instance Arbitrary I.HexInt where
+  arbitrary = I.HexInt <$> arbitrary
+
+instance Arbitrary S.ExtRouter where
+  arbitrary = S.ExtRouter <$> arbitrary <*> arbitrary <*> arbitrary
+
+instance Arbitrary S.ExtSwitch where
+  arbitrary = S.ExtSwitch <$> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary
+
+instance Arbitrary S.L2 where
+  arbitrary = S.L2 <$> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary
+
+instance Arbitrary S.L3 where
+  arbitrary = S.L3 <$> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary
+
+instance Arbitrary S.L4 where
+  arbitrary = S.L4 <$> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary
+
+instance Arbitrary S.RawHeader where
+  arbitrary = S.RawHeader <$> arbitrary <*> arbitrary <*> arbitrary
+
+instance Arbitrary S.Records where
+  arbitrary = S.Records <$> arbitrary <*> arbitrary <*> arbitrary
+
+instance Arbitrary S.SamplesElt where
+  arbitrary = S.SamplesElt <$> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary
+
+instance Arbitrary S.SFlow where
+  arbitrary = S.SFlow <$> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary
+
+word8 :: Gen Word8
+word8 = arbitrary `suchThat` (\x -> x >= 0 && x <= 255)
+
+instance Arbitrary N5.FlowsElt where
+  arbitrary = N5.FlowsElt <$> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary
+
+instance Arbitrary N5.Header where
+  arbitrary = N5.Header <$> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary
+
+instance Arbitrary N5.NetFlow where
+  arbitrary = N5.NetFlow <$> arbitrary <*> arbitrary <*> arbitrary
+
+instance Arbitrary N9.HexInt where
+  arbitrary = N9.HexInt <$> arbitrary
+
+instance Arbitrary N9.DataSetsEltElt where
+  arbitrary = N9.DataSetsEltElt <$> arbitrary <*> arbitrary
+
+instance Arbitrary N9.Header where
+  arbitrary = N9.Header <$> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary
+
+instance Arbitrary N9.NetFlow where
+  arbitrary = N9.NetFlow <$> arbitrary <*> arbitrary <*> arbitrary
diff --git a/test/UnitTests.hs b/test/UnitTests.hs
new file mode 100644
--- /dev/null
+++ b/test/UnitTests.hs
@@ -0,0 +1,63 @@
+{-# language QuasiQuotes #-}
+{-# language ScopedTypeVariables #-}
+
+module Main (main) where
+
+import Data.ByteString (ByteString)
+import NeatInterpolation (text)
+import System.Exit (exitFailure, exitSuccess)
+import VFlow.Types.IpFix
+import VFlow.Types.SFlow
+import qualified VFlow.Types.NetFlow5 as N5
+import qualified VFlow.Types.NetFlow9 as N9
+import Data.Foldable (fold)
+import qualified Data.Aeson as Aeson
+import qualified Data.Text.Encoding as TE
+
+main :: IO ()
+main = exit $ mconcat [ ipFix, sflow, netflow5 ]
+
+ipFix :: Exit
+ipFix = eitherToExit (Aeson.eitherDecodeStrict ipfixJson :: Either String IpFix)
+
+sflow :: Exit
+sflow = eitherToExit (Aeson.eitherDecodeStrict sflowJson :: Either String SFlow)
+
+netflow5 :: Exit
+netflow5 = eitherToExit (Aeson.eitherDecodeStrict netflow5Json :: Either String N5.NetFlow)
+
+netflow9 :: Exit
+netflow9 = eitherToExit (Aeson.eitherDecodeStrict netflow9Json :: Either String N9.NetFlow)
+
+eitherToExit :: Either a b -> Exit
+eitherToExit = either (const ExitFail) (const ExitSucc)
+
+exit :: Exit -> IO ()
+exit ExitFail = exitFailure
+exit ExitSucc = exitSuccess
+ 
+data Exit = ExitFail | ExitSucc
+
+instance Semigroup Exit where
+  ExitFail <> _ = ExitFail
+  _ <> ExitFail = ExitFail
+  _ <> _ = ExitSucc
+
+instance Monoid Exit where
+  mempty = ExitSucc
+
+sflowJson :: ByteString
+sflowJson = TE.encodeUtf8 $
+  [text|{"Version":5,"IPVersion":1,"AgentSubID":5,"SequenceNo":37591,"SysUpTime":3287084017,"SamplesNo":1,"Samples":[{"SequenceNo":1530345639,"SourceID":0,"SamplingRate":4096,"SamplePool":1938456576,"Drops":0,"Input":536,"Output":728,"RecordsNo":3,"Records":{"ExtRouter":{"NextHop":"115.131.251.90","SrcMask":24,"DstMask":14},"ExtSwitch":{"SrcVlan":0,"SrcPriority":0,"DstVlan":0,"DstPriority":0},"RawHeader":{"L2":{"SrcMAC":"58:00:bb:e7:57:6f","DstMAC":"f4:a7:39:44:a8:27","Vlan":0,"EtherType":2048},"L3":{"Version":4,"TOS":0,"TotalLen":1452,"ID":13515,"Flags":0,"FragOff":0,"TTL":62,"Protocol":6,"Checksum":8564,"Src":"10.1.8.5","Dst":"161.140.24.181"},"L4":{"SrcPort":443,"DstPort":56521,"DataOffset":5,"Reserved":0,"Flags":16}}}}],"IPAddress":"192.168.10.0"}|]
+
+ipfixJson :: ByteString
+ipfixJson = TE.encodeUtf8 $
+  [text|{"AgentID":"192.168.21.15","Header":{"Version":10,"Length":420,"ExportTime":1483484642,"SequenceNo":1434533677,"DomainID":32771},"DataSets":[[{"I":8,"V":"192.16.28.217"},{"I":12,"V":"180.10.210.240"},{"I":5,"V":2},{"I":4,"V":6},{"I":7,"V":443},{"I":11,"V":64381},{"I":32,"V":0},{"I":10,"V":811},{"I":58,"V":0},{"I":9,"V":24},{"I":13,"V":20},{"I":16,"V":4200000000},{"I":17,"V":27747},{"I":15,"V":"180.105.10.210"},{"I":6,"V":"0x10"},{"I":14,"V":1113},{"I":1,"V":22500},{"I":2,"V":15},{"I":52,"V":63},{"I":53,"V":63},{"I":152,"V":1483484581770},{"I":153,"V":1483484622384},{"I":136,"V":2},{"I":243,"V":0},{"I":245,"V":0}]]}|]
+
+netflow5Json :: ByteString
+netflow5Json = TE.encodeUtf8 $
+  [text|{"AgentID":"114.23.3.231","Header":{"Version":5,"Count":3,"SysUpTimeMSecs":51469784,"UNIXSecs":1544476581,"UNIXNSecs":0,"SeqNum":873873830,"EngType":0,"EngID":0,"SmpInt":1000},"Flows":[{"SrcAddr":"125.238.46.48","DstAddr":"114.23.236.96","NextHop":"114.23.3.231","Input":791,"Output":817,"PktCount":4,"L3Octets":1708,"StartTime":51402145,"EndTime":51433264,"SrcPort":49233,"DstPort":443,"Padding1":0,"TCPFlags":16,"ProtType":6,"Tos":0,"SrcAsNum":4771,"DstAsNum":56030,"SrcMask":20,"DstMask":22,"Padding2":0},{"SrcAddr":"125.238.46.48","DstAddr":"114.23.236.96","NextHop":"114.23.3.231","Input":791,"Output":817,"PktCount":1,"L3Octets":441,"StartTime":51425137,"EndTime":51425137,"SrcPort":49233,"DstPort":443,"Padding1":0,"TCPFlags":24,"ProtType":6,"Tos":0,"SrcAsNum":4771,"DstAsNum":56030,"SrcMask":20,"DstMask":22,"Padding2":0},{"SrcAddr":"210.5.53.48","DstAddr":"103.22.200.210","NextHop":"122.56.118.157","Input":564,"Output":802,"PktCount":1,"L3Octets":1500,"StartTime":51420072,"EndTime":51420072,"SrcPort":80,"DstPort":56108,"Padding1":0,"TCPFlags":16,"ProtType":6,"Tos":0,"SrcAsNum":56030,"DstAsNum":13335,"SrcMask":24,"DstMask":23,"Padding2":0}]}|]
+
+netflow9Json :: ByteString
+netflow9Json = TE.encodeUtf8 $
+  [text|{"AgentID":"10.81.70.56","Header":{"Version":9,"Count":1,"SysUpTime":357280,"UNIXSecs":1493918653,"SeqNum":14,"SrcID":87},"DataSets":[[{"I":1,"V":"0x00000050"},{"I":2,"V":"0x00000002"},{"I":4,"V":2},{"I":5,"V":192},{"I":6,"V":"0x00"},{"I":7,"V":0},{"I":8,"V":"10.81.70.56"},{"I":9,"V":0},{"I":10,"V":0},{"I":11,"V":0},{"I":12,"V":"224.0.0.22"},{"I":13,"V":0},{"I":14,"V":0},{"I":15,"V":"0.0.0.0"},{"I":16,"V":0},{"I":17,"V":0},{"I":21,"V":300044},{"I":22,"V":299144}]]}|]
diff --git a/vflow-types.cabal b/vflow-types.cabal
new file mode 100644
--- /dev/null
+++ b/vflow-types.cabal
@@ -0,0 +1,111 @@
+cabal-version: 2.2
+name:
+  vflow-types
+version:
+  0.1
+synopsis:
+  types for ingesting vflow data with aeson
+description:
+  `vflow-types` provides types suitable for ingesting vflow data with aeson.
+  .
+  Verizon Digital's <https://github.com/VerizonDigital/vflow vflow> is a network flow collector. Features:
+  .
+  * IPFIX RFC7011 collector
+  .
+  * sFlow v5 raw header / counters collector
+  .
+  * Netflow v5 collector
+  .
+  * Netflow v9 collector
+  .
+  * Decoding sFlow raw header L2/L3/L4
+  .
+  * Producer to Apache Kafka, NSQ, NATS
+  .
+  * Replicate IPFIX to 3rd party collector
+  .
+  * Support for IPv4 and IPv6
+  .
+  * Monitoring with InfluxDB and OpenTSDB backend
+  .
+  * Easy integration with JUNOS
+  .
+  Note that this is not an official Verizon Digital product.
+homepage:
+  https://github.com/chessai/vflow-types
+bug-reports:
+  https://github.com/chessai/vflow-types/issues
+license:
+  BSD-3-Clause
+license-file:
+  LICENSE
+author:
+  chessai
+maintainer:
+  chessai <chessai1996@gmail.com>
+copyright:
+  © 2019 chessai
+category:
+  Utility,Data,Parsing,Text
+build-type:
+  Simple
+extra-doc-files:
+    README.md
+  , CHANGELOG.md
+tested-with:
+  GHC == 8.4.4, GHC == 8.6.3
+
+library
+  hs-source-dirs:
+    src
+  exposed-modules:
+    VFlow.Types.IpFix
+    VFlow.Types.SFlow
+    VFlow.Types.NetFlow5
+    VFlow.Types.NetFlow9
+  build-depends:
+    , aeson >= 1 && < 1.5
+    , base >= 4.10.1 && < 4.13
+    , ip >= 1.5 && < 1.6
+    , json-alt >= 1 && < 1.1
+    , json-autotype >= 3 && < 4
+    , scientific >= 0.3 && < 0.4
+    , text >= 1.2 && < 1.3
+  ghc-options:
+    -Wall
+  default-language:
+    Haskell2010
+
+test-suite unit-tests
+  type: exitcode-stdio-1.0
+  hs-source-dirs: test
+  main-is: UnitTests.hs
+  build-depends:
+    , base
+    , vflow-types
+    , bytestring
+    , text
+    , aeson
+    , neat-interpolation
+  default-language:
+    Haskell2010
+
+test-suite laws
+  type: exitcode-stdio-1.0
+  hs-source-dirs: test
+  main-is: Laws.hs
+  build-depends:
+    , base
+    , vflow-types
+    , QuickCheck
+    , quickcheck-classes
+    , json-alt
+    , ip
+  default-language:
+    Haskell2010
+
+source-repository head
+  type:
+    git
+  location:
+    https://github.com/chessai/vflow-types.git
