diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,3 @@
+# 0.0.0.0
+
+- Initial release.
diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,201 @@
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright © 2022–2025 Jonathan Knowles
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
diff --git a/README.md b/README.md
new file mode 100644
--- /dev/null
+++ b/README.md
@@ -0,0 +1,6 @@
+# `monoidmap-aeson`
+<a href="https://jonathanknowles.github.io/monoidmap-aeson/"><img src="https://img.shields.io/badge/API-Documentation-227755" /></a>
+
+## Overview
+
+This package provides JSON serialisation support for the [`monoidmap`](https://hackage.haskell.org/package/monoidmap) package with [`aeson`](https://hackage.haskell.org/package/aeson).
diff --git a/components/monoidmap-aeson-test/Data/MonoidMap/JSONSpec.hs b/components/monoidmap-aeson-test/Data/MonoidMap/JSONSpec.hs
new file mode 100644
--- /dev/null
+++ b/components/monoidmap-aeson-test/Data/MonoidMap/JSONSpec.hs
@@ -0,0 +1,99 @@
+{-# LANGUAGE ExistentialQuantification #-}
+{-# OPTIONS_GHC -fno-warn-orphans #-}
+
+-- |
+-- Copyright: © 2022–2025 Jonathan Knowles
+-- License: Apache-2.0
+--
+module Data.MonoidMap.JSONSpec
+    ( spec
+    )
+where
+
+import Prelude
+
+import Control.Monad
+    ( forM_
+    )
+import Data.MonoidMap
+    ( MonoidMap
+    )
+import Data.MonoidMap.JSON
+    (
+    )
+import Data.Proxy
+    ( Proxy (Proxy)
+    )
+import Data.Text
+    ( Text
+    )
+import Test.Aeson.Internal.GoldenSpecs
+    ( goldenSpecs
+    )
+import Test.Common
+    ( Test
+    , TestKey
+    , TestValueType (TestValueType)
+    , makeSpec
+    , testValueTypesAll
+    )
+import Test.Hspec
+    ( Spec
+    , describe
+    )
+import Test.QuickCheck.Classes
+    ( jsonLaws
+    )
+import Test.QuickCheck.Classes.Hspec
+    ( testLaws
+    )
+
+import qualified Test.Aeson.Internal.Utils as Golden
+
+spec :: Spec
+spec = do
+    describe "JSON"
+        $ forM_ testKeyValueTypes
+        $ \(TestKeyType k, TestValueType v) -> specForTypes k v
+  where
+    testKeyValueTypes =
+        [(kt, vt) | kt <- testKeyTypes, vt <- testValueTypesAll]
+
+specForTypes :: forall k v. (Test k v) => Proxy k -> Proxy v -> Spec
+specForTypes = makeSpec $ do
+    testLaws @(MonoidMap k v) jsonLaws
+    goldenSpecs goldenSettings (Proxy @(MonoidMap k v))
+
+goldenSettings :: Golden.Settings
+goldenSettings =
+    Golden.defaultSettings
+        { Golden.goldenDirectoryOption =
+            Golden.CustomDirectoryName "golden"
+        , Golden.comparisonFile =
+            Golden.OverwriteGoldenFile
+        , Golden.randomMismatchOption =
+            Golden.RandomMismatchError
+        , Golden.useModuleNameAsSubDirectory =
+            False
+        , Golden.sampleSize =
+            10
+        }
+
+data TestKeyType = forall k. (TestKey k) => TestKeyType (Proxy k)
+
+testKeyTypes :: [TestKeyType]
+testKeyTypes =
+    mconcat [testKeyTypes_textual, testKeyTypes_nonTextual]
+  where
+    -- A selection of key types for which keys are encoded as JSON strings.
+    -- For these types, 'MonoidMap' objects are encoded as JSON objects.
+    testKeyTypes_textual =
+        [ TestKeyType (Proxy @Int)
+        , TestKeyType (Proxy @Text)
+        ]
+    -- A selection of key types for which keys are NOT encoded as JSON strings.
+    -- For these types, 'MonoidMap' objects are encoded as JSON arrays.
+    testKeyTypes_nonTextual =
+        [ TestKeyType (Proxy @[Int])
+        , TestKeyType (Proxy @(Int, Int))
+        ]
diff --git a/components/monoidmap-aeson-test/Spec.hs b/components/monoidmap-aeson-test/Spec.hs
new file mode 100644
--- /dev/null
+++ b/components/monoidmap-aeson-test/Spec.hs
@@ -0,0 +1,1 @@
+{-# OPTIONS_GHC -F -pgmF hspec-discover #-}
diff --git a/components/monoidmap-aeson-test/SpecHook.hs b/components/monoidmap-aeson-test/SpecHook.hs
new file mode 100644
--- /dev/null
+++ b/components/monoidmap-aeson-test/SpecHook.hs
@@ -0,0 +1,6 @@
+module SpecHook where
+
+import Test.Hspec
+
+hook :: Spec -> Spec
+hook = parallel
diff --git a/components/monoidmap-aeson-test/Test/Common.hs b/components/monoidmap-aeson-test/Test/Common.hs
new file mode 100644
--- /dev/null
+++ b/components/monoidmap-aeson-test/Test/Common.hs
@@ -0,0 +1,197 @@
+{-# LANGUAGE ExistentialQuantification #-}
+{-# OPTIONS_GHC -fno-warn-orphans #-}
+{- HLINT ignore "Redundant bracket" -}
+{- HLINT ignore "Use camelCase" -}
+{- HLINT ignore "Use null" -}
+
+-- |
+-- Copyright: © 2022–2025 Jonathan Knowles
+-- License: Apache-2.0
+--
+module Test.Common
+    ( Key
+    , Test
+    , TestKey
+    , TestValueType (..)
+    , testValueTypesAll
+    , TestValue
+    , makeSpec
+    , property
+    ) where
+
+import Prelude
+
+import Data.Aeson
+    ( FromJSON
+    , FromJSONKey
+    , ToJSON
+    , ToJSONKey
+    )
+import Data.Kind
+    ( Constraint, Type )
+import Data.Monoid
+    ( Dual, Product, Sum )
+import Data.Monoid.Null
+    ( MonoidNull )
+import Data.MonoidMap
+    ( MonoidMap )
+import Data.MonoidMap.JSON
+    ()
+import Data.Proxy
+    ( Proxy (Proxy) )
+import Data.Set
+    ( Set )
+import Data.Text
+    ( Text )
+import Data.Typeable
+    ( Typeable, typeRep )
+import GHC.Exts
+    ( IsList (..) )
+import Numeric.Natural
+    ( Natural )
+import Test.Hspec
+    ( Spec, describe )
+import Test.Key
+    ( Key2, Key4 )
+import Test.QuickCheck
+    ( Arbitrary (..)
+    , CoArbitrary (..)
+    , Function (..)
+    , Property
+    , Testable
+    , arbitrarySizedIntegral
+    , checkCoverage
+    , coarbitraryIntegral
+    , coarbitraryShow
+    , frequency
+    , functionIntegral
+    , functionMap
+    , functionShow
+    , listOf
+    , scale
+    , shrinkIntegral
+    , shrinkMapBy
+    )
+
+import qualified Data.MonoidMap as MonoidMap
+import qualified Data.Text as Text
+import qualified Test.QuickCheck as QC
+
+--------------------------------------------------------------------------------
+-- Arbitrary instances
+--------------------------------------------------------------------------------
+
+instance (Arbitrary k, Ord k, Arbitrary v, MonoidNull v) =>
+    Arbitrary (MonoidMap k v)
+  where
+    arbitrary =
+        fromList <$> scale (`mod` 16) (listOf ((,) <$> arbitrary <*> arbitrary))
+    shrink =
+        shrinkMapBy MonoidMap.fromMap MonoidMap.toMap shrink
+
+instance (CoArbitrary k, CoArbitrary v) =>
+    CoArbitrary (MonoidMap k v)
+  where
+    coarbitrary = coarbitrary . MonoidMap.toMap
+
+instance (Function k, Function v, Ord k, MonoidNull v) =>
+    Function (MonoidMap k v)
+  where
+    function = functionMap MonoidMap.toMap MonoidMap.fromMap
+
+instance Arbitrary Natural where
+    arbitrary = arbitrarySizedIntegral
+    shrink = shrinkIntegral
+
+instance CoArbitrary Natural where
+    coarbitrary = coarbitraryIntegral
+
+instance Function Natural where
+    function = functionIntegral
+
+instance Arbitrary Text where
+    arbitrary = Text.pack <$> listOf genChar
+      where
+        genChar = frequency
+            [ (64, pure 'a')
+            , (16, pure 'b')
+            , ( 4, pure 'c')
+            , ( 1, pure 'd')
+            ]
+
+instance CoArbitrary Text where
+    coarbitrary = coarbitraryShow
+
+instance Function Text where
+    function = functionShow
+
+--------------------------------------------------------------------------------
+-- Test keys
+--------------------------------------------------------------------------------
+
+type SmallKey = Key2
+type Key = Key4
+
+--------------------------------------------------------------------------------
+-- Test constraints
+--------------------------------------------------------------------------------
+
+type Test k v = (TestKey k, TestValue v)
+
+type TestKey k =
+    ( Arbitrary k
+    , CoArbitrary k
+    , Function k
+    , Ord k
+    , Show k
+    , Typeable k
+    , ToJSONKey k
+    , FromJSONKey k
+    )
+
+type TestValue v =
+    ( Arbitrary v
+    , CoArbitrary v
+    , Eq v
+    , Function v
+    , MonoidNull v
+    , Show v
+    , Typeable v
+    , ToJSON v
+    , FromJSON v
+    )
+
+--------------------------------------------------------------------------------
+-- Test value types
+--------------------------------------------------------------------------------
+
+data TestValueType (c :: Type -> Constraint) =
+    forall v. (TestValue v, c v) => TestValueType (Proxy v)
+
+testValueTypesAll :: [TestValueType MonoidNull]
+testValueTypesAll =
+    [ TestValueType (Proxy @(Dual Text))
+    , TestValueType (Proxy @(Dual [Int]))
+    , TestValueType (Proxy @(Dual [Natural]))
+    , TestValueType (Proxy @(Product Int))
+    , TestValueType (Proxy @(Product Natural))
+    , TestValueType (Proxy @(Set Int))
+    , TestValueType (Proxy @(Set Natural))
+    , TestValueType (Proxy @(Sum Int))
+    , TestValueType (Proxy @(Sum Natural))
+    , TestValueType (Proxy @(Text))
+    , TestValueType (Proxy @[Int])
+    , TestValueType (Proxy @[Natural])
+    , TestValueType (Proxy @(MonoidMap SmallKey (Sum Int)))
+    , TestValueType (Proxy @(MonoidMap SmallKey (Sum Natural)))
+    ]
+
+--------------------------------------------------------------------------------
+-- Utilities
+--------------------------------------------------------------------------------
+
+makeSpec :: forall k v. Test k v => Spec -> Proxy k -> Proxy v -> Spec
+makeSpec spec _k _v = describe (show $ typeRep (Proxy @(MonoidMap k v))) spec
+
+property :: Testable t => t -> Property
+property = checkCoverage . QC.property
diff --git a/components/monoidmap-aeson-test/Test/Key.hs b/components/monoidmap-aeson-test/Test/Key.hs
new file mode 100644
--- /dev/null
+++ b/components/monoidmap-aeson-test/Test/Key.hs
@@ -0,0 +1,91 @@
+{-# LANGUAGE DataKinds #-}
+{-# LANGUAGE DeriveAnyClass #-}
+{-# LANGUAGE DeriveGeneric #-}
+
+-- |
+-- Copyright: © 2022–2025 Jonathan Knowles
+-- License: Apache-2.0
+--
+-- Quasi-unique keys.
+--
+module Test.Key
+    ( Key1
+    , Key2
+    , Key4
+    , Key8
+    )
+where
+
+import Prelude
+
+import Data.Aeson.Types
+    ( FromJSON (parseJSON)
+    , FromJSONKey (fromJSONKey)
+    , FromJSONKeyFunction (FromJSONKeyTextParser)
+    , Parser
+    , ToJSON (toEncoding, toJSON)
+    , ToJSONKey (toJSONKey)
+    , toJSONKeyText
+    , withText
+    )
+import Data.Text
+    ( Text
+    )
+import GHC.Generics
+    ( Generic
+    )
+import GHC.TypeLits
+    ( Nat
+    )
+import Test.QuickCheck
+    ( Arbitrary
+    , CoArbitrary
+    , Function
+    )
+import Test.QuickCheck.Quid
+    ( Latin (Latin)
+    , Quid
+    , Size (Size)
+    )
+import Text.Read
+    ( readMaybe
+    )
+
+import qualified Data.Text as Text
+
+newtype Key (size :: Nat) = Key Quid
+    deriving stock (Eq, Generic, Ord)
+    deriving (Read, Show) via Latin Quid
+    deriving (Arbitrary) via Size size Quid
+    deriving (CoArbitrary) via Quid
+    deriving anyclass (Function)
+
+type Key1 = Key 1
+type Key2 = Key 2
+type Key4 = Key 4
+type Key8 = Key 8
+
+instance ToJSON (Key size) where
+    toEncoding = toEncoding . toText
+    toJSON = toJSON . toText
+
+instance ToJSONKey (Key size) where
+    toJSONKey = toJSONKeyText toText
+
+instance FromJSON (Key size) where
+    parseJSON = withText "Key" parseFromText
+
+instance FromJSONKey (Key size) where
+    fromJSONKey = FromJSONKeyTextParser parseFromText
+
+toText :: Key size -> Text
+toText = Text.dropAround (== '\"') . Text.pack . show
+
+maybeFromText :: Text -> Maybe (Key size)
+maybeFromText = readMaybe . show . Text.unpack
+
+parseFromText :: Text -> Parser (Key size)
+parseFromText =
+    maybe (fail failureMessage) pure . maybeFromText
+  where
+    failureMessage = "Failed to parse key from JSON"
diff --git a/components/monoidmap-aeson-test/Test/QuickCheck/Classes/Hspec.hs b/components/monoidmap-aeson-test/Test/QuickCheck/Classes/Hspec.hs
new file mode 100644
--- /dev/null
+++ b/components/monoidmap-aeson-test/Test/QuickCheck/Classes/Hspec.hs
@@ -0,0 +1,63 @@
+{-# LANGUAGE PolyKinds #-}
+
+-- |
+-- Copyright: © 2022–2025 Jonathan Knowles
+-- License: Apache-2.0
+--
+-- Provides testing functions to check that type class instances obey laws.
+--
+module Test.QuickCheck.Classes.Hspec
+    ( testLaws
+    , testLawsMany
+    ) where
+
+import Prelude
+
+import Control.Monad
+    ( forM_ )
+import Data.Proxy
+    ( Proxy (..) )
+import Data.Typeable
+    ( Typeable, typeRep )
+import Test.Hspec
+    ( Spec, describe, it, parallel )
+import Test.QuickCheck.Classes
+    ( Laws (..) )
+
+-- | Constructs a test to check that the given type class instance obeys the
+--   given set of laws.
+--
+-- Example usage:
+--
+-- >>> testLaws @Natural ordLaws
+-- >>> testLaws @(Map Int) functorLaws
+--
+testLaws
+    :: forall a. Typeable a
+    => (Proxy a -> Laws)
+    -> Spec
+testLaws getLaws =
+    parallel $ describe description $
+        forM_ (lawsProperties laws) $ uncurry it
+  where
+    description = mconcat
+        [ "Testing "
+        , lawsTypeclass laws
+        , " laws for type "
+        , show (typeRep $ Proxy @a)
+        ]
+    laws = getLaws $ Proxy @a
+
+-- | Calls `testLaws` with multiple sets of laws.
+--
+-- Example usage:
+--
+-- >>> testLawsMany @Natural [eqLaws, ordLaws]
+-- >>> testLawsMany @(Map Int) [foldableLaws, functorLaws]
+--
+testLawsMany
+    :: forall a. Typeable a
+    => [Proxy a -> Laws]
+    -> Spec
+testLawsMany getLawsMany =
+    testLaws @a `mapM_` getLawsMany
diff --git a/components/monoidmap-aeson/Data/MonoidMap/JSON.hs b/components/monoidmap-aeson/Data/MonoidMap/JSON.hs
new file mode 100644
--- /dev/null
+++ b/components/monoidmap-aeson/Data/MonoidMap/JSON.hs
@@ -0,0 +1,131 @@
+{-# OPTIONS_GHC -Wno-orphans #-}
+
+-- |
+-- Copyright: © 2022–2025 Jonathan Knowles
+-- License: Apache-2.0
+--
+module Data.MonoidMap.JSON
+    (
+    -- * Introduction
+    -- $_introduction
+
+    -- * Examples
+    -- $_examples
+
+    -- * Laws
+    -- $_laws
+    )
+    where
+
+import Prelude
+
+import Data.Aeson
+    ( FromJSON (parseJSON)
+    , FromJSONKey
+    , ToJSON (toEncoding, toJSON)
+    , ToJSONKey
+    , encode
+    )
+import Data.Map.Strict
+    ( Map
+    )
+import Data.Monoid
+    ( Sum
+    )
+import Data.Monoid.Null
+    ( MonoidNull
+    )
+import Data.MonoidMap
+    ( MonoidMap
+    , fromList
+    , fromMap
+    , toMap
+    )
+
+-- $_introduction
+-- #_introduction#
+--
+-- This module provides instances of 'ToJSON' and 'FromJSON' for 'MonoidMap'.
+--
+-- These instances provide objects of type 'MonoidMap' __@k@__ __@v@__ with a
+-- JSON encoding that is /identical/ to objects of type 'Map' __@k@__ __@v@__,
+-- which are serialised as either JSON /objects/ or /arrays/ depending on the
+-- key type __@k@__.
+
+-- $_examples
+-- #_examples#
+--
+-- === Encoding as JSON objects
+--
+-- @
+-- >>> 'encode' '$' 'MonoidMap'.'fromList' [("abc", 'Sum' 1), ("def", 'Sum' 2)]
+--
+-- "{\\"abc\\":1,\\"def\\":2}"
+-- @
+--
+-- === Encoding as JSON arrays
+--
+-- @
+-- >>> 'encode' '$' 'MonoidMap'.'fromList' [((1,2), 'Sum' 3), ((2,3), 'Sum' 5)]
+--
+-- "[[[1,2],3],[[2,3],5]]"
+-- @
+
+-- $_laws
+-- #_laws#
+--
+-- === Encoding to JSON
+--
+-- The 'ToJSON' instance satisfies the following laws:
+--
+-- @
+-- 'toEncoding' '==' 'toEncoding' '.' 'MonoidMap'.'toMap'
+-- 'toJSON'     '==' 'toJSON'     '.' 'MonoidMap'.'toMap'
+-- @
+--
+-- === Decoding from JSON
+--
+-- The 'FromJSON' instance satisfies the following law:
+--
+-- @
+-- 'parseJSON' '==' 'fmap' ('fmap' 'MonoidMap'.'fromMap') 'parseJSON'
+-- @
+--
+-- Mappings from keys to values that decode to 'mempty' are __not__ included in
+-- decoded 'MonoidMap' objects.
+
+_importsRequiredForDocumentation :: ()
+_importsRequiredForDocumentation = ()
+  where
+    _encode = encode ()
+
+    _fromList :: (Ord k, MonoidNull v) => [(k, v)] -> MonoidMap k v
+    _fromList = fromList
+
+    _fromMap :: (Ord k, MonoidNull v) => Map k v -> MonoidMap k v
+    _fromMap = fromMap
+
+    _toMap :: (Ord k, MonoidNull v) => MonoidMap k v -> Map k v
+    _toMap = toMap
+
+    _Sum :: Sum ()
+    _Sum = undefined
+
+instance
+    ( ToJSONKey k
+    , ToJSON v
+    )
+    => ToJSON (MonoidMap k v)
+  where
+    toEncoding = toEncoding . toMap
+    toJSON = toJSON . toMap
+
+instance
+    ( FromJSONKey k
+    , Ord k
+    , FromJSON v
+    , MonoidNull v
+    )
+    => FromJSON (MonoidMap k v)
+  where
+    parseJSON = fmap (fmap fromMap) parseJSON
diff --git a/monoidmap-aeson.cabal b/monoidmap-aeson.cabal
new file mode 100644
--- /dev/null
+++ b/monoidmap-aeson.cabal
@@ -0,0 +1,113 @@
+cabal-version:  3.0
+name:           monoidmap-aeson
+version:        0.0.0.0
+bug-reports:    https://github.com/jonathanknowles/monoidmap-aeson/issues
+license:        Apache-2.0
+license-file:   LICENSE
+author:         Jonathan Knowles
+maintainer:     mail@jonathanknowles.net
+copyright:      2022–2025 Jonathan Knowles
+category:       Data Structures
+synopsis:       JSON support for monoidmap.
+description:    JSON support for the monoidmap package, compatible with aeson.
+
+extra-doc-files:
+    CHANGELOG.md
+    README.md
+
+common dependency-aeson
+    build-depends:aeson                         >= 2.2.3.0    && < 2.3
+common dependency-base
+    build-depends:base                          >= 4.14.3.0   && < 4.22
+common dependency-containers
+    build-depends:containers                    >= 0.6.5.1    && < 0.8
+common dependency-hspec
+    build-depends:hspec                         >= 2.10.9     && < 2.12
+common dependency-hspec-golden-aeson
+    build-depends:hspec-golden-aeson            >= 0.9.0.0    && < 0.10
+common dependency-monoid-subclasses
+    build-depends:monoid-subclasses             >= 1.2.3      && < 1.3
+common dependency-monoidmap
+    build-depends:monoidmap                     >= 0.0.3.0    && < 0.1
+common dependency-QuickCheck
+    build-depends:QuickCheck                    >= 2.14.2     && < 2.16
+common dependency-quickcheck-classes
+    build-depends:quickcheck-classes            >= 0.6.5.0    && < 0.7
+common dependency-quickcheck-quid
+    build-depends:quickcheck-quid               >= 0.0.1.7    && < 0.1
+common dependency-text
+    build-depends:text                          >= 1.2.4.1    && < 2.2
+
+common extensions
+    default-extensions:
+        BangPatterns
+        ConstraintKinds
+        DerivingStrategies
+        DerivingVia
+        FlexibleContexts
+        FlexibleInstances
+        GeneralizedNewtypeDeriving
+        LambdaCase
+        MultiParamTypeClasses
+        NoImplicitPrelude
+        NumericUnderscores
+        ScopedTypeVariables
+        TupleSections
+        TypeApplications
+        TypeFamilies
+        TypeOperators
+        ViewPatterns
+
+source-repository head
+    type: git
+    location: https://github.com/jonathanknowles/monoidmap-aeson
+
+library
+    import:
+      , dependency-aeson
+      , dependency-base
+      , dependency-containers
+      , dependency-monoid-subclasses
+      , dependency-monoidmap
+      , extensions
+    hs-source-dirs:
+        components/monoidmap-aeson
+    exposed-modules:
+        Data.MonoidMap.JSON
+    default-language:
+        Haskell2010
+
+test-suite monoidmap-aeson-test
+    import:
+      , dependency-aeson
+      , dependency-base
+      , dependency-containers
+      , dependency-hspec
+      , dependency-hspec-golden-aeson
+      , dependency-monoid-subclasses
+      , dependency-monoidmap
+      , dependency-QuickCheck
+      , dependency-quickcheck-classes
+      , dependency-quickcheck-quid
+      , dependency-text
+      , extensions
+    build-depends:
+      , monoidmap-aeson
+    ghc-options:
+        -threaded -with-rtsopts=-N
+    main-is:
+        Spec.hs
+    hs-source-dirs:
+        components/monoidmap-aeson-test
+    other-modules:
+        SpecHook
+        Data.MonoidMap.JSONSpec
+        Test.Common
+        Test.QuickCheck.Classes.Hspec
+        Test.Key
+    type:
+        exitcode-stdio-1.0
+    default-language:
+        Haskell2010
+    build-tool-depends:
+        hspec-discover:hspec-discover ==2.*
