diff --git a/src/Allwmake b/src/Allwmake
index a2109cae24d40c5231193c55bd517a433e84942f..e488f9397822ef92eef76442a0ac7560ed00db3f 100755
--- a/src/Allwmake
+++ b/src/Allwmake
@@ -55,6 +55,7 @@ wmake $targetType finiteVolume
 wmake $targetType mesh/blockMesh
 wmake $targetType mesh/extrudeModel  # Requires: blockMesh
 wmake $targetType dynamicMesh  # Requires: extrudeModel
+wmake $targetType genericPatchFields
 
 wmake $targetType parallel/decompose/decompositionMethods
 
@@ -100,8 +101,6 @@ wmake $targetType waveModels
 
 wmake $targetType engine
 
-wmake $targetType genericPatchFields
-
 conversion/Allwmake $targetType $*
 
 functionObjects/Allwmake $targetType $*
diff --git a/src/genericPatchFields/Make/files b/src/genericPatchFields/Make/files
index 2210ca7cdc31fa22b285c9a0a83b6988d294ac2f..17ebcb95faab1884eb34b820f719c818c41bab59 100644
--- a/src/genericPatchFields/Make/files
+++ b/src/genericPatchFields/Make/files
@@ -1,6 +1,8 @@
+genericPatchFieldBase/genericPatchFieldBase.C
+
 genericFaPatchField/genericFaPatchFields.C
 genericFvPatchField/genericFvPatchFields.C
-genericPointPatchField/genericPointPatchFields.C
 genericFvsPatchField/genericFvsPatchFields.C
+genericPointPatchField/genericPointPatchFields.C
 
 LIB = $(FOAM_LIBBIN)/libgenericPatchFields
diff --git a/src/genericPatchFields/genericFaPatchField/genericFaPatchField.C b/src/genericPatchFields/genericFaPatchField/genericFaPatchField.C
index fc9f319ccc197df0db52c5a9e5074ba1c2071cde..00bfded5d76761855268f3b418198a9e4fceea05 100644
--- a/src/genericPatchFields/genericFaPatchField/genericFaPatchField.C
+++ b/src/genericPatchFields/genericFaPatchField/genericFaPatchField.C
@@ -38,12 +38,12 @@ Foam::genericFaPatchField<Type>::genericFaPatchField
     const DimensionedField<Type, areaMesh>& iF
 )
 :
-    calculatedFaPatchField<Type>(p, iF)
+    parent_bctype(p, iF)
 {
     FatalErrorInFunction
-        << "Trying to construct an genericFaPatchField on patch "
+        << "Trying to construct genericFaPatchField on patch "
         << this->patch().name()
-        << " of field " << this->internalField().name()
+        << " of field " << this->internalField().name() << nl
         << abort(FatalError);
 }
 
@@ -56,545 +56,86 @@ Foam::genericFaPatchField<Type>::genericFaPatchField
     const dictionary& dict
 )
 :
-    calculatedFaPatchField<Type>(p, iF, dict),
-    actualTypeName_(dict.get<word>("type")),
-    dict_(dict)
+    parent_bctype(p, iF, dict),
+    genericPatchFieldBase(dict)
 {
     const label patchSize = this->size();
+    const word& patchName = this->patch().name();
+    const IOobject& io = this->internalField();
 
     if (!dict.found("value"))
     {
-        FatalIOErrorInFunction(dict)
-            << nl << "    Cannot find 'value' entry"
-            << " on patch " << this->patch().name()
-            << " of field " << this->internalField().name()
-            << " in file " << this->internalField().objectPath() << nl
-            << "    which is required to set the"
-               " values of the generic patch field." << nl
-            << "    (Actual type " << actualTypeName_ << ')' << nl << nl
-            << "    Please add the 'value' entry to the write function"
-               " of the user-defined boundary-condition" << nl
-            << exit(FatalIOError);
+        reportMissingEntry("value", patchName, io);
     }
 
-    for (const entry& dEntry : dict_)
-    {
-        const keyType& key = dEntry.keyword();
-
-        if
-        (
-            key == "type"
-         || key == "value"
-         || !dEntry.isStream() || dEntry.stream().empty()
-        )
-        {
-            continue;
-        }
-
-
-        ITstream& is = dEntry.stream();
-
-        // Read first token
-        token firstToken(is);
-
-        if (firstToken.isWord("nonuniform"))
-        {
-            token fieldToken(is);
-
-            if (!fieldToken.isCompound())
-            {
-                if
-                (
-                    fieldToken.isLabel()
-                 && fieldToken.labelToken() == 0
-                )
-                {
-                    scalarFields_.insert(key, autoPtr<scalarField>::New());
-                }
-                else
-                {
-                    FatalIOErrorInFunction(dict)
-                        << "\n    token following 'nonuniform' "
-                           "is not a compound"
-                        << "\n    on patch " << this->patch().name()
-                        << " of field "
-                        << this->internalField().name()
-                        << " in file "
-                        << this->internalField().objectPath() << nl
-                        << exit(FatalIOError);
-                }
-            }
-            else if
-            (
-                fieldToken.compoundToken().type()
-             == token::Compound<List<scalar>>::typeName
-            )
-            {
-                auto fPtr = autoPtr<scalarField>::New();
-
-                fPtr->transfer
-                (
-                    dynamicCast<token::Compound<List<scalar>>>
-                    (
-                        fieldToken.transferCompoundToken(is)
-                    )
-                );
-
-                if (fPtr->size() != patchSize)
-                {
-                    FatalIOErrorInFunction(dict)
-                        << "\n    size of field " << key
-                        << " (" << fPtr->size() << ')'
-                        << " is not the same size as the patch ("
-                        << patchSize << ')'
-                        << "\n    on patch " << this->patch().name()
-                        << " of field "
-                        << this->internalField().name()
-                        << " in file "
-                        << this->internalField().objectPath() << nl
-                        << exit(FatalIOError);
-                }
-
-                scalarFields_.insert(key, fPtr);
-            }
-            else if
-            (
-                fieldToken.compoundToken().type()
-             == token::Compound<List<vector>>::typeName
-            )
-            {
-                auto fPtr = autoPtr<vectorField>::New();
-
-                fPtr->transfer
-                (
-                    dynamicCast<token::Compound<List<vector>>>
-                    (
-                        fieldToken.transferCompoundToken(is)
-                    )
-                );
-
-                if (fPtr->size() != patchSize)
-                {
-                    FatalIOErrorInFunction(dict)
-                        << "\n    size of field " << key
-                        << " (" << fPtr->size() << ')'
-                        << " is not the same size as the patch ("
-                        << patchSize << ')'
-                        << "\n    on patch " << this->patch().name()
-                        << " of field "
-                        << this->internalField().name()
-                        << " in file "
-                        << this->internalField().objectPath() << nl
-                        << exit(FatalIOError);
-                }
-
-                vectorFields_.insert(key, fPtr);
-            }
-            else if
-            (
-                fieldToken.compoundToken().type()
-             == token::Compound<List<sphericalTensor>>::typeName
-            )
-            {
-                auto fPtr = autoPtr<sphericalTensorField>::New();
-
-                fPtr->transfer
-                (
-                    dynamicCast<token::Compound<List<sphericalTensor>>>
-                    (
-                        fieldToken.transferCompoundToken(is)
-                    )
-                );
-
-                if (fPtr->size() != patchSize)
-                {
-                    FatalIOErrorInFunction(dict)
-                        << "\n    size of field " << key
-                        << " (" << fPtr->size() << ')'
-                        << " is not the same size as the patch ("
-                        << patchSize << ')'
-                        << "\n    on patch " << this->patch().name()
-                        << " of field "
-                        << this->internalField().name()
-                        << " in file "
-                        << this->internalField().objectPath() << nl
-                        << exit(FatalIOError);
-                }
-
-                sphTensorFields_.insert(key, fPtr);
-            }
-            else if
-            (
-                fieldToken.compoundToken().type()
-             == token::Compound<List<symmTensor>>::typeName
-            )
-            {
-                auto fPtr = autoPtr<symmTensorField>::New();
-
-                fPtr->transfer
-                (
-                    dynamicCast<token::Compound<List<symmTensor>>>
-                    (
-                        fieldToken.transferCompoundToken(is)
-                    )
-                );
-
-                if (fPtr->size() != patchSize)
-                {
-                    FatalIOErrorInFunction(dict)
-                        << "\n    size of field " << key
-                        << " (" << fPtr->size() << ')'
-                        << " is not the same size as the patch ("
-                        << patchSize << ')'
-                        << "\n    on patch " << this->patch().name()
-                        << " of field "
-                        << this->internalField().name()
-                        << " in file "
-                        << this->internalField().objectPath() << nl
-                        << exit(FatalIOError);
-                }
-
-                symmTensorFields_.insert(key, fPtr);
-            }
-            else if
-            (
-                fieldToken.compoundToken().type()
-             == token::Compound<List<tensor>>::typeName
-            )
-            {
-                auto fPtr = autoPtr<tensorField>::New();
-
-                fPtr->transfer
-                (
-                    dynamicCast<token::Compound<List<tensor>>>
-                    (
-                        fieldToken.transferCompoundToken(is)
-                    )
-                );
-
-                if (fPtr->size() != patchSize)
-                {
-                    FatalIOErrorInFunction(dict)
-                        << "\n    size of field " << key
-                        << " (" << fPtr->size() << ')'
-                        << " is not the same size as the patch ("
-                        << patchSize << ')'
-                        << "\n    on patch " << this->patch().name()
-                        << " of field "
-                        << this->internalField().name()
-                        << " in file "
-                        << this->internalField().objectPath() << nl
-                        << exit(FatalIOError);
-                }
-
-                tensorFields_.insert(key, fPtr);
-            }
-            else
-            {
-                FatalIOErrorInFunction(dict)
-                    << "\n    compound " << fieldToken.compoundToken()
-                    << " not supported"
-                    << "\n    on patch " << this->patch().name()
-                    << " of field "
-                    << this->internalField().name()
-                    << " in file "
-                    << this->internalField().objectPath() << nl
-                    << exit(FatalIOError);
-            }
-        }
-        else if (firstToken.isWord("uniform"))
-        {
-            token fieldToken(is);
-
-            if (!fieldToken.isPunctuation())
-            {
-                scalarFields_.insert
-                (
-                    key,
-                    autoPtr<scalarField>::New
-                    (
-                        patchSize,
-                        fieldToken.number()
-                    )
-                );
-            }
-            else
-            {
-                // Read as scalarList.
-                is.putBack(fieldToken);
-
-                scalarList l(is);
-
-                if (l.size() == vector::nComponents)
-                {
-                    vector vs(l[0], l[1], l[2]);
-
-                    vectorFields_.insert
-                    (
-                        key,
-                        autoPtr<vectorField>::New
-                        (
-                            patchSize,
-                            vs
-                        )
-                    );
-                }
-                else if (l.size() == sphericalTensor::nComponents)
-                {
-                    sphericalTensor vs(l[0]);
-
-                    sphTensorFields_.insert
-                    (
-                        key,
-                        autoPtr<sphericalTensorField>::New
-                        (
-                            patchSize,
-                            vs
-                        )
-                    );
-                }
-                else if (l.size() == symmTensor::nComponents)
-                {
-                    symmTensor vs(l[0], l[1], l[2], l[3], l[4], l[5]);
-
-                    symmTensorFields_.insert
-                    (
-                        key,
-                        autoPtr<symmTensorField>::New
-                        (
-                            patchSize,
-                            vs
-                        )
-                    );
-                }
-                else if (l.size() == tensor::nComponents)
-                {
-                    tensor vs
-                    (
-                        l[0], l[1], l[2],
-                        l[3], l[4], l[5],
-                        l[6], l[7], l[8]
-                    );
-
-                    tensorFields_.insert
-                    (
-                        key,
-                        autoPtr<tensorField>::New
-                        (
-                            patchSize,
-                            vs
-                        )
-                    );
-                }
-                else
-                {
-                    FatalIOErrorInFunction(dict)
-                        << "\n    unrecognised native type " << l
-                        << "\n    on patch " << this->patch().name()
-                        << " of field "
-                        << this->internalField().name()
-                        << " in file "
-                        << this->internalField().objectPath() << nl
-                        << exit(FatalIOError);
-                }
-            }
-        }
-    }
+    // Handle "value" separately
+    processGeneric(patchSize, patchName, io, true);
 }
 
 
 template<class Type>
 Foam::genericFaPatchField<Type>::genericFaPatchField
 (
-    const genericFaPatchField<Type>& ptf,
+    const genericFaPatchField<Type>& rhs,
     const faPatch& p,
     const DimensionedField<Type, areaMesh>& iF,
     const faPatchFieldMapper& mapper
 )
 :
-    calculatedFaPatchField<Type>(ptf, p, iF, mapper),
-    actualTypeName_(ptf.actualTypeName_),
-    dict_(ptf.dict_)
+    parent_bctype(rhs, p, iF, mapper),
+    genericPatchFieldBase(zero{}, rhs)
 {
-    forAllConstIters(ptf.scalarFields_, iter)
-    {
-        scalarFields_.insert
-        (
-            iter.key(),
-            autoPtr<scalarField>::New(*iter(), mapper)
-        );
-    }
-
-    forAllConstIters(ptf.vectorFields_, iter)
-    {
-        vectorFields_.insert
-        (
-            iter.key(),
-            autoPtr<vectorField>::New(*iter(), mapper)
-        );
-    }
-
-    forAllConstIters(ptf.sphTensorFields_, iter)
-    {
-        sphTensorFields_.insert
-        (
-            iter.key(),
-            autoPtr<sphericalTensorField>::New(*iter(), mapper)
-        );
-    }
-
-    forAllConstIters(ptf.symmTensorFields_, iter)
-    {
-        symmTensorFields_.insert
-        (
-            iter.key(),
-            autoPtr<symmTensorField>::New(*iter(), mapper)
-        );
-    }
-
-    forAllConstIters(ptf.tensorFields_, iter)
-    {
-        tensorFields_.insert
-        (
-            iter.key(),
-            autoPtr<tensorField>::New(*iter(), mapper)
-        );
-    }
+    this->mapGeneric(rhs, mapper);
 }
 
 
 template<class Type>
 Foam::genericFaPatchField<Type>::genericFaPatchField
 (
-    const genericFaPatchField<Type>& ptf
-)
-:
-    calculatedFaPatchField<Type>(ptf),
-    actualTypeName_(ptf.actualTypeName_),
-    dict_(ptf.dict_),
-    scalarFields_(ptf.scalarFields_),
-    vectorFields_(ptf.vectorFields_),
-    sphTensorFields_(ptf.sphTensorFields_),
-    symmTensorFields_(ptf.symmTensorFields_),
-    tensorFields_(ptf.tensorFields_)
-{}
-
-
-template<class Type>
-Foam::genericFaPatchField<Type>::genericFaPatchField
-(
-    const genericFaPatchField<Type>& ptf,
+    const genericFaPatchField<Type>& rhs,
     const DimensionedField<Type, areaMesh>& iF
 )
 :
-    calculatedFaPatchField<Type>(ptf, iF),
-    actualTypeName_(ptf.actualTypeName_),
-    dict_(ptf.dict_),
-    scalarFields_(ptf.scalarFields_),
-    vectorFields_(ptf.vectorFields_),
-    sphTensorFields_(ptf.sphTensorFields_),
-    symmTensorFields_(ptf.symmTensorFields_),
-    tensorFields_(ptf.tensorFields_)
+    parent_bctype(rhs, iF),
+    genericPatchFieldBase(rhs)
 {}
 
 
 // * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
 
+template<class Type>
+void Foam::genericFaPatchField<Type>::write(Ostream& os) const
+{
+    // Handle "value" separately
+    genericPatchFieldBase::writeGeneric(os, true);
+    this->writeEntry("value", os);
+}
+
+
 template<class Type>
 void Foam::genericFaPatchField<Type>::autoMap
 (
     const faPatchFieldMapper& m
 )
 {
-    calculatedFaPatchField<Type>::autoMap(m);
-
-    forAllIters(scalarFields_, iter)
-    {
-        (*iter)->autoMap(m);
-    }
-
-    forAllIters(vectorFields_, iter)
-    {
-        (*iter)->autoMap(m);
-    }
-
-    forAllIters(sphTensorFields_, iter)
-    {
-        (*iter)->autoMap(m);
-    }
-
-    forAllIters(symmTensorFields_, iter)
-    {
-        (*iter)->autoMap(m);
-    }
-
-    forAllIters(tensorFields_, iter)
-    {
-        (*iter)->autoMap(m);
-    }
+    parent_bctype::autoMap(m);
+    this->autoMapGeneric(m);
 }
 
 
 template<class Type>
 void Foam::genericFaPatchField<Type>::rmap
 (
-    const faPatchField<Type>& ptf,
+    const faPatchField<Type>& rhs,
     const labelList& addr
 )
 {
-    calculatedFaPatchField<Type>::rmap(ptf, addr);
-
-    const genericFaPatchField<Type>& dptf =
-        refCast<const genericFaPatchField<Type>>(ptf);
-
-    forAllIters(scalarFields_, iter )
-    {
-        const auto iter2 = dptf.scalarFields_.cfind(iter.key());
-
-        if (iter.found())
-        {
-            (*iter)->rmap(*iter2(), addr);
-        }
-    }
-
-    forAllIters(vectorFields_, iter)
-    {
-        const auto iter2 = dptf.vectorFields_.cfind(iter.key());
-
-        if (iter.found())
-        {
-            (*iter)->rmap(*iter2(), addr);
-        }
-    }
-
-    forAllIters(sphTensorFields_, iter)
-    {
-        const auto iter2 = dptf.sphTensorFields_.cfind(iter.key());
-
-        if (iter.found())
-        {
-            (*iter)->rmap(*iter2(), addr);
-        }
-    }
+    parent_bctype::rmap(rhs, addr);
 
-    forAllIters(symmTensorFields_, iter)
+    const auto* base = isA<genericPatchFieldBase>(rhs);
+    if (base)
     {
-        const auto iter2 = dptf.symmTensorFields_.cfind(iter.key());
-
-        if (iter.found())
-        {
-            (*iter)->rmap(*iter2(), addr);
-        }
-    }
-
-    forAllIters(tensorFields_, iter)
-    {
-        const auto iter2 = dptf.tensorFields_.find(iter.key());
-
-        if (iter.found())
-        {
-            (*iter)->rmap(*iter2(), addr);
-        }
+        this->rmapGeneric(*base, addr);
     }
 }
 
@@ -607,14 +148,14 @@ Foam::genericFaPatchField<Type>::valueInternalCoeffs
 ) const
 {
     FatalErrorInFunction
-        << "cannot be called for a genericFaPatchField"
-           " (actual type " << actualTypeName_ << ")"
-        << "\n    on patch " << this->patch().name()
-        << " of field " << this->internalField().name()
-        << " in file " << this->internalField().objectPath()
-        << "\n    You are probably trying to solve for a field with a "
-           "generic boundary condition."
-        << abort(FatalError);
+        << "Cannot be called for a genericFaPatchField";
+
+    genericFatalSolveError
+    (
+        this->patch().name(),
+        this->internalField()
+    );
+    FatalError << abort(FatalError);
 
     return *this;
 }
@@ -628,14 +169,14 @@ Foam::genericFaPatchField<Type>::valueBoundaryCoeffs
 ) const
 {
     FatalErrorInFunction
-        << "cannot be called for a genericFaPatchField"
-           " (actual type " << actualTypeName_ << ")"
-        << "\n    on patch " << this->patch().name()
-        << " of field " << this->internalField().name()
-        << " in file " << this->internalField().objectPath()
-        << "\n    You are probably trying to solve for a field with a "
-           "generic boundary condition."
-        << abort(FatalError);
+        << "Cannot be called for a genericFaPatchField";
+
+    genericFatalSolveError
+    (
+        this->patch().name(),
+        this->internalField()
+    );
+    FatalError << abort(FatalError);
 
     return *this;
 }
@@ -646,92 +187,34 @@ Foam::tmp<Foam::Field<Type>>
 Foam::genericFaPatchField<Type>::gradientInternalCoeffs() const
 {
     FatalErrorInFunction
-        << "cannot be called for a genericFaPatchField"
-           " (actual type " << actualTypeName_ << ")"
-        << "\n    on patch " << this->patch().name()
-        << " of field " << this->internalField().name()
-        << " in file " << this->internalField().objectPath()
-        << "\n    You are probably trying to solve for a field with a "
-           "generic boundary condition."
-        << abort(FatalError);
+        << "Cannot be called for a genericFaPatchField";
+
+    genericFatalSolveError
+    (
+        this->patch().name(),
+        this->internalField()
+    );
+    FatalError << abort(FatalError);
 
     return *this;
 }
 
+
 template<class Type>
 Foam::tmp<Foam::Field<Type>>
 Foam::genericFaPatchField<Type>::gradientBoundaryCoeffs() const
 {
     FatalErrorInFunction
-        << "cannot be called for a genericFaPatchField"
-           " (actual type " << actualTypeName_ << ")"
-        << "\n    on patch " << this->patch().name()
-        << " of field " << this->internalField().name()
-        << " in file " << this->internalField().objectPath()
-        << "\n    You are probably trying to solve for a field with a "
-           "generic boundary condition."
-        << abort(FatalError);
-
-    return *this;
-}
-
-
-template<class Type>
-const Foam::word& Foam::genericFaPatchField<Type>::actualType() const
-{
-    return actualTypeName_;
-}
-
+        << "Cannot be called for a genericFaPatchField";
 
-template<class Type>
-void Foam::genericFaPatchField<Type>::write(Ostream& os) const
-{
-    os.writeEntry("type", actualTypeName_);
+    genericFatalSolveError
+    (
+        this->patch().name(),
+        this->internalField()
+    );
+    FatalError << abort(FatalError);
 
-    for (const entry& dEntry : dict_)
-    {
-        const keyType& key = dEntry.keyword();
-
-        if (key == "type" || key == "value")
-        {
-            // NB: "type" written first, "value" written last
-            continue;
-        }
-        else if
-        (
-            dEntry.isStream()
-         && dEntry.stream().size()
-         && dEntry.stream()[0].isWord("nonuniform")
-        )
-        {
-            if (scalarFields_.found(key))
-            {
-                scalarFields_.cfind(key)()->writeEntry(key, os);
-            }
-            else if (vectorFields_.found(key))
-            {
-                vectorFields_.cfind(key)()->writeEntry(key, os);
-            }
-            else if (sphTensorFields_.found(key))
-            {
-                sphTensorFields_.cfind(key)()->writeEntry(key, os);
-            }
-            else if (symmTensorFields_.found(key))
-            {
-                symmTensorFields_.cfind(key)()->writeEntry(key, os);
-            }
-            else if (tensorFields_.found(key))
-            {
-                tensorFields_.cfind(key)()->writeEntry(key, os);
-            }
-        }
-        else
-        {
-            dEntry.write(os);
-        }
-    }
-
-    this->writeEntry("value", os);
+    return *this;
 }
 
 
diff --git a/src/genericPatchFields/genericFaPatchField/genericFaPatchField.H b/src/genericPatchFields/genericFaPatchField/genericFaPatchField.H
index b4e05daaeedc214ca0f76f39efd7e6088df357bc..fece0812dad8b00b1cf56eb561ce2ffd7c68126c 100644
--- a/src/genericPatchFields/genericFaPatchField/genericFaPatchField.H
+++ b/src/genericPatchFields/genericFaPatchField/genericFaPatchField.H
@@ -6,7 +6,7 @@
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
     Copyright (C) 2011-2016 OpenFOAM Foundation
-    Copyright (C) 2019 OpenCFD Ltd.
+    Copyright (C) 2019-2021 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -44,7 +44,7 @@ SourceFiles
 #define genericFaPatchField_H
 
 #include "calculatedFaPatchField.H"
-#include "HashPtrTable.H"
+#include "genericPatchFieldBase.H"
 
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
 
@@ -58,19 +58,11 @@ namespace Foam
 template<class Type>
 class genericFaPatchField
 :
-    public calculatedFaPatchField<Type>
+    public calculatedFaPatchField<Type>,
+    public genericPatchFieldBase
 {
-    // Private Data
-
-        const word actualTypeName_;
-
-        dictionary dict_;
-
-        HashPtrTable<scalarField> scalarFields_;
-        HashPtrTable<vectorField> vectorFields_;
-        HashPtrTable<sphericalTensorField> sphTensorFields_;
-        HashPtrTable<symmTensorField> symmTensorFields_;
-        HashPtrTable<tensorField> tensorFields_;
+    //- The parent boundary condition type
+    typedef calculatedFaPatchField<Type> parent_bctype;
 
 
 public:
@@ -105,12 +97,6 @@ public:
             const faPatchFieldMapper&
         );
 
-        //- Construct as copy
-        genericFaPatchField
-        (
-            const genericFaPatchField<Type>&
-        );
-
         //- Construct and return a clone
         virtual tmp<faPatchField<Type>> clone() const
         {
@@ -120,6 +106,9 @@ public:
             );
         }
 
+        //- Default construct
+        genericFaPatchField(const genericFaPatchField<Type>&) = default;
+
         //- Construct as copy setting internal field reference
         genericFaPatchField
         (
@@ -142,52 +131,38 @@ public:
 
     // Member Functions
 
-        // Mapping Functions
-
-            //- Map (and resize as needed) from self given a mapping object
-            virtual void autoMap
-            (
-                const faPatchFieldMapper&
-            );
+        //- Write
+        virtual void write(Ostream&) const;
 
-            //- Reverse map the given faPatchField onto this faPatchField
-            virtual void rmap
-            (
-                const faPatchField<Type>&,
-                const labelList&
-            );
 
+    // Mapping Functions
 
-        // Evaluation Functions
+        //- Map (and resize as needed) from self given a mapping object
+        virtual void autoMap(const faPatchFieldMapper&);
 
-            //- Return the matrix diagonal coefficients corresponding to the
-            //  evaluation of the value of this patchField with given weights
-            virtual tmp<Field<Type>> valueInternalCoeffs
-            (
-                const tmp<scalarField>&
-            ) const;
+        //- Reverse map the given faPatchField onto this faPatchField
+        virtual void rmap
+        (
+            const faPatchField<Type>&,
+            const labelList&
+        );
 
-            //- Return the matrix source coefficients corresponding to the
-            //  evaluation of the value of this patchField with given weights
-            virtual tmp<Field<Type>> valueBoundaryCoeffs
-            (
-                const tmp<scalarField>&
-            ) const;
 
-            //- Return the matrix diagonal coefficients corresponding to the
-            //  evaluation of the gradient of this patchField
-            tmp<Field<Type>> gradientInternalCoeffs() const;
+    // Evaluation Functions
 
-            //- Return the matrix source coefficients corresponding to the
-            //  evaluation of the gradient of this patchField
-            tmp<Field<Type>> gradientBoundaryCoeffs() const;
+        //- Fatal
+        virtual tmp<Field<Type>>
+        valueInternalCoeffs(const tmp<scalarField>&) const;
 
+        //- Fatal
+        virtual tmp<Field<Type>>
+        valueBoundaryCoeffs(const tmp<scalarField>&) const;
 
-        //- Return the actual type
-        const word& actualType() const;
+        //- Fatal
+        tmp<Field<Type>> gradientInternalCoeffs() const;
 
-        //- Write
-        virtual void write(Ostream&) const;
+        //- Fatal
+        tmp<Field<Type>> gradientBoundaryCoeffs() const;
 };
 
 
diff --git a/src/genericPatchFields/genericFvPatchField/genericFvPatchField.C b/src/genericPatchFields/genericFvPatchField/genericFvPatchField.C
index 3ce915e5ec60c21dafea89b83840efd920cd372f..0f72c34600b40662d12e45a2ae7442232ee2c302 100644
--- a/src/genericPatchFields/genericFvPatchField/genericFvPatchField.C
+++ b/src/genericPatchFields/genericFvPatchField/genericFvPatchField.C
@@ -38,7 +38,7 @@ Foam::genericFvPatchField<Type>::genericFvPatchField
     const DimensionedField<Type, volMesh>& iF
 )
 :
-    calculatedFvPatchField<Type>(p, iF)
+    parent_bctype(p, iF)
 {
     FatalErrorInFunction
         << "Trying to construct an genericFvPatchField on patch "
@@ -56,545 +56,86 @@ Foam::genericFvPatchField<Type>::genericFvPatchField
     const dictionary& dict
 )
 :
-    calculatedFvPatchField<Type>(p, iF, dict),
-    actualTypeName_(dict.get<word>("type")),
-    dict_(dict)
+    parent_bctype(p, iF, dict),
+    genericPatchFieldBase(dict)
 {
     const label patchSize = this->size();
+    const word& patchName = this->patch().name();
+    const IOobject& io = this->internalField();
 
     if (!dict.found("value"))
     {
-        FatalIOErrorInFunction(dict)
-            << nl << "    Cannot find 'value' entry"
-            << " on patch " << this->patch().name()
-            << " of field " << this->internalField().name()
-            << " in file " << this->internalField().objectPath() << nl
-            << "    which is required to set the"
-               " values of the generic patch field." << nl
-            << "    (Actual type " << actualTypeName_ << ')' << nl << nl
-            << "    Please add the 'value' entry to the write function"
-               " of the user-defined boundary-condition" << nl
-            << exit(FatalIOError);
+        reportMissingEntry("value", patchName, io);
     }
 
-    for (const entry& dEntry : dict_)
-    {
-        const keyType& key = dEntry.keyword();
-
-        if
-        (
-            key == "type"
-         || key == "value"
-         || !dEntry.isStream() || dEntry.stream().empty()
-        )
-        {
-            continue;
-        }
-
-
-        ITstream& is = dEntry.stream();
-
-        // Read first token
-        token firstToken(is);
-
-        if (firstToken.isWord("nonuniform"))
-        {
-            token fieldToken(is);
-
-            if (!fieldToken.isCompound())
-            {
-                if
-                (
-                    fieldToken.isLabel()
-                 && fieldToken.labelToken() == 0
-                )
-                {
-                    scalarFields_.insert(key, autoPtr<scalarField>::New());
-                }
-                else
-                {
-                    FatalIOErrorInFunction(dict)
-                        << "\n    token following 'nonuniform' "
-                           "is not a compound"
-                        << "\n    on patch " << this->patch().name()
-                        << " of field "
-                        << this->internalField().name()
-                        << " in file "
-                        << this->internalField().objectPath() << nl
-                        << exit(FatalIOError);
-                }
-            }
-            else if
-            (
-                fieldToken.compoundToken().type()
-             == token::Compound<List<scalar>>::typeName
-            )
-            {
-                auto fPtr = autoPtr<scalarField>::New();
-
-                fPtr->transfer
-                (
-                    dynamicCast<token::Compound<List<scalar>>>
-                    (
-                        fieldToken.transferCompoundToken(is)
-                    )
-                );
-
-                if (fPtr->size() != patchSize)
-                {
-                    FatalIOErrorInFunction(dict)
-                        << "\n    size of field " << key
-                        << " (" << fPtr->size() << ')'
-                        << " is not the same size as the patch ("
-                        << patchSize << ')'
-                        << "\n    on patch " << this->patch().name()
-                        << " of field "
-                        << this->internalField().name()
-                        << " in file "
-                        << this->internalField().objectPath() << nl
-                        << exit(FatalIOError);
-                }
-
-                scalarFields_.insert(key, fPtr);
-            }
-            else if
-            (
-                fieldToken.compoundToken().type()
-             == token::Compound<List<vector>>::typeName
-            )
-            {
-                auto fPtr = autoPtr<vectorField>::New();
-
-                fPtr->transfer
-                (
-                    dynamicCast<token::Compound<List<vector>>>
-                    (
-                        fieldToken.transferCompoundToken(is)
-                    )
-                );
-
-                if (fPtr->size() != patchSize)
-                {
-                    FatalIOErrorInFunction(dict)
-                        << "\n    size of field " << key
-                        << " (" << fPtr->size() << ')'
-                        << " is not the same size as the patch ("
-                        << patchSize << ')'
-                        << "\n    on patch " << this->patch().name()
-                        << " of field "
-                        << this->internalField().name()
-                        << " in file "
-                        << this->internalField().objectPath() << nl
-                        << exit(FatalIOError);
-                }
-
-                vectorFields_.insert(key, fPtr);
-            }
-            else if
-            (
-                fieldToken.compoundToken().type()
-             == token::Compound<List<sphericalTensor>>::typeName
-            )
-            {
-                auto fPtr = autoPtr<sphericalTensorField>::New();
-
-                fPtr->transfer
-                (
-                    dynamicCast<token::Compound<List<sphericalTensor>>>
-                    (
-                        fieldToken.transferCompoundToken(is)
-                    )
-                );
-
-                if (fPtr->size() != patchSize)
-                {
-                    FatalIOErrorInFunction(dict)
-                        << "\n    size of field " << key
-                        << " (" << fPtr->size() << ')'
-                        << " is not the same size as the patch ("
-                        << patchSize << ')'
-                        << "\n    on patch " << this->patch().name()
-                        << " of field "
-                        << this->internalField().name()
-                        << " in file "
-                        << this->internalField().objectPath() << nl
-                        << exit(FatalIOError);
-                }
-
-                sphTensorFields_.insert(key, fPtr);
-            }
-            else if
-            (
-                fieldToken.compoundToken().type()
-             == token::Compound<List<symmTensor>>::typeName
-            )
-            {
-                auto fPtr = autoPtr<symmTensorField>::New();
-
-                fPtr->transfer
-                (
-                    dynamicCast<token::Compound<List<symmTensor>>>
-                    (
-                        fieldToken.transferCompoundToken(is)
-                    )
-                );
-
-                if (fPtr->size() != patchSize)
-                {
-                    FatalIOErrorInFunction(dict)
-                        << "\n    size of field " << key
-                        << " (" << fPtr->size() << ')'
-                        << " is not the same size as the patch ("
-                        << patchSize << ')'
-                        << "\n    on patch " << this->patch().name()
-                        << " of field "
-                        << this->internalField().name()
-                        << " in file "
-                        << this->internalField().objectPath() << nl
-                        << exit(FatalIOError);
-                }
-
-                symmTensorFields_.insert(key, fPtr);
-            }
-            else if
-            (
-                fieldToken.compoundToken().type()
-             == token::Compound<List<tensor>>::typeName
-            )
-            {
-                auto fPtr = autoPtr<tensorField>::New();
-
-                fPtr->transfer
-                (
-                    dynamicCast<token::Compound<List<tensor>>>
-                    (
-                        fieldToken.transferCompoundToken(is)
-                    )
-                );
-
-                if (fPtr->size() != patchSize)
-                {
-                    FatalIOErrorInFunction(dict)
-                        << "\n    size of field " << key
-                        << " (" << fPtr->size() << ')'
-                        << " is not the same size as the patch ("
-                        << patchSize << ')'
-                        << "\n    on patch " << this->patch().name()
-                        << " of field "
-                        << this->internalField().name()
-                        << " in file "
-                        << this->internalField().objectPath() << nl
-                        << exit(FatalIOError);
-                }
-
-                tensorFields_.insert(key, fPtr);
-            }
-            else
-            {
-                FatalIOErrorInFunction(dict)
-                    << "\n    compound " << fieldToken.compoundToken()
-                    << " not supported"
-                    << "\n    on patch " << this->patch().name()
-                    << " of field "
-                    << this->internalField().name()
-                    << " in file "
-                    << this->internalField().objectPath() << nl
-                    << exit(FatalIOError);
-            }
-        }
-        else if (firstToken.isWord("uniform"))
-        {
-            token fieldToken(is);
-
-            if (!fieldToken.isPunctuation())
-            {
-                scalarFields_.insert
-                (
-                    key,
-                    autoPtr<scalarField>::New
-                    (
-                        patchSize,
-                        fieldToken.number()
-                    )
-                );
-            }
-            else
-            {
-                // Read as scalarList.
-                is.putBack(fieldToken);
-
-                scalarList l(is);
-
-                if (l.size() == vector::nComponents)
-                {
-                    vector vs(l[0], l[1], l[2]);
-
-                    vectorFields_.insert
-                    (
-                        key,
-                        autoPtr<vectorField>::New
-                        (
-                            patchSize,
-                            vs
-                        )
-                    );
-                }
-                else if (l.size() == sphericalTensor::nComponents)
-                {
-                    sphericalTensor vs(l[0]);
-
-                    sphTensorFields_.insert
-                    (
-                        key,
-                        autoPtr<sphericalTensorField>::New
-                        (
-                            patchSize,
-                            vs
-                        )
-                    );
-                }
-                else if (l.size() == symmTensor::nComponents)
-                {
-                    symmTensor vs(l[0], l[1], l[2], l[3], l[4], l[5]);
-
-                    symmTensorFields_.insert
-                    (
-                        key,
-                        autoPtr<symmTensorField>::New
-                        (
-                            patchSize,
-                            vs
-                        )
-                    );
-                }
-                else if (l.size() == tensor::nComponents)
-                {
-                    tensor vs
-                    (
-                        l[0], l[1], l[2],
-                        l[3], l[4], l[5],
-                        l[6], l[7], l[8]
-                    );
-
-                    tensorFields_.insert
-                    (
-                        key,
-                        autoPtr<tensorField>::New
-                        (
-                            patchSize,
-                            vs
-                        )
-                    );
-                }
-                else
-                {
-                    FatalIOErrorInFunction(dict)
-                        << "\n    unrecognised native type " << l
-                        << "\n    on patch " << this->patch().name()
-                        << " of field "
-                        << this->internalField().name()
-                        << " in file "
-                        << this->internalField().objectPath() << nl
-                        << exit(FatalIOError);
-                }
-            }
-        }
-    }
+    // Handle "value" separately
+    processGeneric(patchSize, patchName, io, true);
 }
 
 
 template<class Type>
 Foam::genericFvPatchField<Type>::genericFvPatchField
 (
-    const genericFvPatchField<Type>& ptf,
+    const genericFvPatchField<Type>& rhs,
     const fvPatch& p,
     const DimensionedField<Type, volMesh>& iF,
     const fvPatchFieldMapper& mapper
 )
 :
-    calculatedFvPatchField<Type>(ptf, p, iF, mapper),
-    actualTypeName_(ptf.actualTypeName_),
-    dict_(ptf.dict_)
+    parent_bctype(rhs, p, iF, mapper),
+    genericPatchFieldBase(zero{}, rhs)
 {
-    forAllConstIters(ptf.scalarFields_, iter)
-    {
-        scalarFields_.insert
-        (
-            iter.key(),
-            autoPtr<scalarField>::New(*iter(), mapper)
-        );
-    }
-
-    forAllConstIters(ptf.vectorFields_, iter)
-    {
-        vectorFields_.insert
-        (
-            iter.key(),
-            autoPtr<vectorField>::New(*iter(), mapper)
-        );
-    }
-
-    forAllConstIters(ptf.sphTensorFields_, iter)
-    {
-        sphTensorFields_.insert
-        (
-            iter.key(),
-            autoPtr<sphericalTensorField>::New(*iter(), mapper)
-        );
-    }
-
-    forAllConstIters(ptf.symmTensorFields_, iter)
-    {
-        symmTensorFields_.insert
-        (
-            iter.key(),
-            autoPtr<symmTensorField>::New(*iter(), mapper)
-        );
-    }
-
-    forAllConstIters(ptf.tensorFields_, iter)
-    {
-        tensorFields_.insert
-        (
-            iter.key(),
-            autoPtr<tensorField>::New(*iter(), mapper)
-        );
-    }
+    this->mapGeneric(rhs, mapper);
 }
 
 
 template<class Type>
 Foam::genericFvPatchField<Type>::genericFvPatchField
 (
-    const genericFvPatchField<Type>& ptf
-)
-:
-    calculatedFvPatchField<Type>(ptf),
-    actualTypeName_(ptf.actualTypeName_),
-    dict_(ptf.dict_),
-    scalarFields_(ptf.scalarFields_),
-    vectorFields_(ptf.vectorFields_),
-    sphTensorFields_(ptf.sphTensorFields_),
-    symmTensorFields_(ptf.symmTensorFields_),
-    tensorFields_(ptf.tensorFields_)
-{}
-
-
-template<class Type>
-Foam::genericFvPatchField<Type>::genericFvPatchField
-(
-    const genericFvPatchField<Type>& ptf,
+    const genericFvPatchField<Type>& rhs,
     const DimensionedField<Type, volMesh>& iF
 )
 :
-    calculatedFvPatchField<Type>(ptf, iF),
-    actualTypeName_(ptf.actualTypeName_),
-    dict_(ptf.dict_),
-    scalarFields_(ptf.scalarFields_),
-    vectorFields_(ptf.vectorFields_),
-    sphTensorFields_(ptf.sphTensorFields_),
-    symmTensorFields_(ptf.symmTensorFields_),
-    tensorFields_(ptf.tensorFields_)
+    parent_bctype(rhs, iF),
+    genericPatchFieldBase(rhs)
 {}
 
 
 // * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
 
+template<class Type>
+void Foam::genericFvPatchField<Type>::write(Ostream& os) const
+{
+    // Handle "value" separately
+    genericPatchFieldBase::writeGeneric(os, true);
+    this->writeEntry("value", os);
+}
+
+
 template<class Type>
 void Foam::genericFvPatchField<Type>::autoMap
 (
     const fvPatchFieldMapper& m
 )
 {
-    calculatedFvPatchField<Type>::autoMap(m);
-
-    forAllIters(scalarFields_, iter)
-    {
-        (*iter)->autoMap(m);
-    }
-
-    forAllIters(vectorFields_, iter)
-    {
-        (*iter)->autoMap(m);
-    }
-
-    forAllIters(sphTensorFields_, iter)
-    {
-        (*iter)->autoMap(m);
-    }
-
-    forAllIters(symmTensorFields_, iter)
-    {
-        (*iter)->autoMap(m);
-    }
-
-    forAllIters(tensorFields_, iter)
-    {
-        (*iter)->autoMap(m);
-    }
+    parent_bctype::autoMap(m);
+    this->autoMapGeneric(m);
 }
 
 
 template<class Type>
 void Foam::genericFvPatchField<Type>::rmap
 (
-    const fvPatchField<Type>& ptf,
+    const fvPatchField<Type>& rhs,
     const labelList& addr
 )
 {
-    calculatedFvPatchField<Type>::rmap(ptf, addr);
+    parent_bctype::rmap(rhs, addr);
 
-    const genericFvPatchField<Type>& dptf =
-        refCast<const genericFvPatchField<Type>>(ptf);
-
-    forAllIters(scalarFields_, iter)
-    {
-        const auto iter2 = dptf.scalarFields_.cfind(iter.key());
-
-        if (iter2.found())
-        {
-            (*iter)->rmap(*iter2(), addr);
-        }
-    }
-
-    forAllIters(vectorFields_, iter)
-    {
-        const auto iter2 = dptf.vectorFields_.find(iter.key());
-
-        if (iter2.found())
-        {
-            (*iter)->rmap(*iter2(), addr);
-        }
-    }
-
-    forAllIters(sphTensorFields_, iter)
+    const auto* base = isA<genericPatchFieldBase>(rhs);
+    if (base)
     {
-        const auto iter2 = dptf.sphTensorFields_.find(iter.key());
-
-        if (iter2.found())
-        {
-            (*iter)->rmap(*iter2(), addr);
-        }
-    }
-
-    forAllIters(symmTensorFields_, iter)
-    {
-        const auto iter2 = dptf.symmTensorFields_.find(iter.key());
-
-        if (iter2.found())
-        {
-            (*iter)->rmap(*iter2(), addr);
-        }
-    }
-
-    forAllIters(tensorFields_, iter)
-    {
-        const auto iter2 = dptf.tensorFields_.find(iter.key());
-
-        if (iter2.found())
-        {
-            (*iter)->rmap(*iter2(), addr);
-        }
+        this->rmapGeneric(*base, addr);
     }
 }
 
@@ -607,14 +148,14 @@ Foam::genericFvPatchField<Type>::valueInternalCoeffs
 ) const
 {
     FatalErrorInFunction
-        << "cannot be called for a genericFvPatchField"
-           " (actual type " << actualTypeName_ << ")"
-        << "\n    on patch " << this->patch().name()
-        << " of field " << this->internalField().name()
-        << " in file " << this->internalField().objectPath()
-        << "\n    You are probably trying to solve for a field with a "
-           "generic boundary condition."
-        << abort(FatalError);
+        << "Cannot be called for a genericFvPatchField";
+
+    genericFatalSolveError
+    (
+        this->patch().name(),
+        this->internalField()
+    );
+    FatalError << abort(FatalError);
 
     return *this;
 }
@@ -628,14 +169,14 @@ Foam::genericFvPatchField<Type>::valueBoundaryCoeffs
 ) const
 {
     FatalErrorInFunction
-        << "cannot be called for a genericFvPatchField"
-           " (actual type " << actualTypeName_ << ")"
-        << "\n    on patch " << this->patch().name()
-        << " of field " << this->internalField().name()
-        << " in file " << this->internalField().objectPath()
-        << "\n    You are probably trying to solve for a field with a "
-           "generic boundary condition."
-        << abort(FatalError);
+        << "Cannot be called for a genericFvPatchField";
+
+    genericFatalSolveError
+    (
+        this->patch().name(),
+        this->internalField()
+    );
+    FatalError << abort(FatalError);
 
     return *this;
 }
@@ -646,14 +187,14 @@ Foam::tmp<Foam::Field<Type>>
 Foam::genericFvPatchField<Type>::gradientInternalCoeffs() const
 {
     FatalErrorInFunction
-        << "cannot be called for a genericFvPatchField"
-           " (actual type " << actualTypeName_ << ")"
-        << "\n    on patch " << this->patch().name()
-        << " of field " << this->internalField().name()
-        << " in file " << this->internalField().objectPath()
-        << "\n    You are probably trying to solve for a field with a "
-           "generic boundary condition."
-        << abort(FatalError);
+        << "Cannot be called for a genericFvPatchField";
+
+    genericFatalSolveError
+    (
+        this->patch().name(),
+        this->internalField()
+    );
+    FatalError << abort(FatalError);
 
     return *this;
 }
@@ -663,74 +204,16 @@ Foam::tmp<Foam::Field<Type>>
 Foam::genericFvPatchField<Type>::gradientBoundaryCoeffs() const
 {
     FatalErrorInFunction
-        << "cannot be called for a genericFvPatchField"
-           " (actual type " << actualTypeName_ << ")"
-        << "\n    on patch " << this->patch().name()
-        << " of field " << this->internalField().name()
-        << " in file " << this->internalField().objectPath()
-        << "\n    You are probably trying to solve for a field with a "
-           "generic boundary condition."
-        << abort(FatalError);
-
-    return *this;
-}
+        << "Cannot be called for a genericFvPatchField";
 
+    genericFatalSolveError
+    (
+        this->patch().name(),
+        this->internalField()
+    );
+    FatalError << abort(FatalError);
 
-template<class Type>
-const Foam::word& Foam::genericFvPatchField<Type>::actualType() const
-{
-    return actualTypeName_;
-}
-
-
-template<class Type>
-void Foam::genericFvPatchField<Type>::write(Ostream& os) const
-{
-    os.writeEntry("type", actualTypeName_);
-
-    for (const entry& dEntry : dict_)
-    {
-        const keyType& key = dEntry.keyword();
-
-        if (key == "type" || key == "value")
-        {
-            continue;
-        }
-        else if
-        (
-            dEntry.isStream()
-         && dEntry.stream().size()
-         && dEntry.stream()[0].isWord("nonuniform")
-        )
-        {
-            if (scalarFields_.found(key))
-            {
-                scalarFields_.cfind(key)()->writeEntry(key, os);
-            }
-            else if (vectorFields_.found(key))
-            {
-                vectorFields_.cfind(key)()->writeEntry(key, os);
-            }
-            else if (sphTensorFields_.found(key))
-            {
-                sphTensorFields_.cfind(key)()->writeEntry(key, os);
-            }
-            else if (symmTensorFields_.found(key))
-            {
-                symmTensorFields_.cfind(key)()->writeEntry(key, os);
-            }
-            else if (tensorFields_.found(key))
-            {
-                tensorFields_.cfind(key)()->writeEntry(key, os);
-            }
-        }
-        else
-        {
-            dEntry.write(os);
-        }
-    }
-
-    this->writeEntry("value", os);
+    return *this;
 }
 
 
diff --git a/src/genericPatchFields/genericFvPatchField/genericFvPatchField.H b/src/genericPatchFields/genericFvPatchField/genericFvPatchField.H
index 7adff19164733b4aa55a485c38736abbc062ce3f..7c527fe050f86ca8d40c7b17cea0ff746c9c9909 100644
--- a/src/genericPatchFields/genericFvPatchField/genericFvPatchField.H
+++ b/src/genericPatchFields/genericFvPatchField/genericFvPatchField.H
@@ -6,7 +6,7 @@
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
     Copyright (C) 2011-2016 OpenFOAM Foundation
-    Copyright (C) 2019 OpenCFD Ltd.
+    Copyright (C) 2019-2021 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -44,7 +44,7 @@ SourceFiles
 #define genericFvPatchField_H
 
 #include "calculatedFvPatchField.H"
-#include "HashPtrTable.H"
+#include "genericPatchFieldBase.H"
 
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
 
@@ -58,19 +58,11 @@ namespace Foam
 template<class Type>
 class genericFvPatchField
 :
-    public calculatedFvPatchField<Type>
+    public calculatedFvPatchField<Type>,
+    public genericPatchFieldBase
 {
-    // Private Data
-
-        const word actualTypeName_;
-
-        dictionary dict_;
-
-        HashPtrTable<scalarField> scalarFields_;
-        HashPtrTable<vectorField> vectorFields_;
-        HashPtrTable<sphericalTensorField> sphTensorFields_;
-        HashPtrTable<symmTensorField> symmTensorFields_;
-        HashPtrTable<tensorField> tensorFields_;
+    //- The parent boundary condition type
+    typedef calculatedFvPatchField<Type> parent_bctype;
 
 
 public:
@@ -105,12 +97,6 @@ public:
             const fvPatchFieldMapper&
         );
 
-        //- Construct as copy
-        genericFvPatchField
-        (
-            const genericFvPatchField<Type>&
-        );
-
         //- Construct and return a clone
         virtual tmp<fvPatchField<Type>> clone() const
         {
@@ -120,6 +106,9 @@ public:
             );
         }
 
+        //- Default copy construct
+        genericFvPatchField(const genericFvPatchField<Type>&) = default;
+
         //- Construct as copy setting internal field reference
         genericFvPatchField
         (
@@ -142,52 +131,38 @@ public:
 
     // Member Functions
 
-        // Mapping Functions
-
-            //- Map (and resize as needed) from self given a mapping object
-            virtual void autoMap
-            (
-                const fvPatchFieldMapper&
-            );
+        //- Write
+        virtual void write(Ostream&) const;
 
-            //- Reverse map the given fvPatchField onto this fvPatchField
-            virtual void rmap
-            (
-                const fvPatchField<Type>&,
-                const labelList&
-            );
 
+    // Mapping Functions
 
-        // Evaluation Functions
+        //- Map (and resize as needed) from self given a mapping object
+        virtual void autoMap(const fvPatchFieldMapper&);
 
-            //- Return the matrix diagonal coefficients corresponding to the
-            //  evaluation of the value of this patchField with given weights
-            virtual tmp<Field<Type>> valueInternalCoeffs
-            (
-                const tmp<scalarField>&
-            ) const;
+        //- Reverse map the given fvPatchField onto this fvPatchField
+        virtual void rmap
+        (
+            const fvPatchField<Type>&,
+            const labelList&
+        );
 
-            //- Return the matrix source coefficients corresponding to the
-            //  evaluation of the value of this patchField with given weights
-            virtual tmp<Field<Type>> valueBoundaryCoeffs
-            (
-                const tmp<scalarField>&
-            ) const;
 
-            //- Return the matrix diagonal coefficients corresponding to the
-            //  evaluation of the gradient of this patchField
-            tmp<Field<Type>> gradientInternalCoeffs() const;
+    // Evaluation Functions
 
-            //- Return the matrix source coefficients corresponding to the
-            //  evaluation of the gradient of this patchField
-            tmp<Field<Type>> gradientBoundaryCoeffs() const;
+        //- Fatal
+        virtual tmp<Field<Type>>
+        valueInternalCoeffs(const tmp<scalarField>&) const;
 
+        //- Fatal
+        virtual tmp<Field<Type>>
+        valueBoundaryCoeffs(const tmp<scalarField>&) const;
 
-        //- Return the actual type
-        const word& actualType() const;
+        //- Fatal
+        tmp<Field<Type>> gradientInternalCoeffs() const;
 
-        //- Write
-        virtual void write(Ostream&) const;
+        //- Fatal
+        tmp<Field<Type>> gradientBoundaryCoeffs() const;
 };
 
 
diff --git a/src/genericPatchFields/genericFvsPatchField/genericFvsPatchField.C b/src/genericPatchFields/genericFvsPatchField/genericFvsPatchField.C
index 8b3af677ff6954fc4785ae89b56b7d425ff56b7c..a982f110cd75b306cc74f9ea34a1341bc299bf8c 100644
--- a/src/genericPatchFields/genericFvsPatchField/genericFvsPatchField.C
+++ b/src/genericPatchFields/genericFvsPatchField/genericFvsPatchField.C
@@ -37,7 +37,7 @@ Foam::genericFvsPatchField<Type>::genericFvsPatchField
     const DimensionedField<Type, surfaceMesh>& iF
 )
 :
-    calculatedFvsPatchField<Type>(p, iF)
+    parent_bctype(p, iF)
 {
     FatalErrorInFunction
         << "Trying to construct an genericFvsPatchField on patch "
@@ -55,545 +55,87 @@ Foam::genericFvsPatchField<Type>::genericFvsPatchField
     const dictionary& dict
 )
 :
-    calculatedFvsPatchField<Type>(p, iF, dict),
-    actualTypeName_(dict.get<word>("type")),
-    dict_(dict)
+    parent_bctype(p, iF, dict),
+    genericPatchFieldBase(dict)
 {
     const label patchSize = this->size();
+    const word& patchName = this->patch().name();
+    const IOobject& io = this->internalField();
 
     if (!dict.found("value"))
     {
-        FatalIOErrorInFunction(dict)
-            << nl << "    Cannot find 'value' entry"
-            << " on patch " << this->patch().name()
-            << " of field " << this->internalField().name()
-            << " in file " << this->internalField().objectPath() << nl
-            << "    which is required to set the"
-               " values of the generic patch field." << nl
-            << "    (Actual type " << actualTypeName_ << ')' << nl << nl
-            << "    Please add the 'value' entry to the write function"
-               " of the user-defined boundary-condition" << nl
-            << exit(FatalIOError);
+        reportMissingEntry("value", patchName, io);
     }
 
-    for (const entry& dEntry : dict_)
-    {
-        const keyType& key = dEntry.keyword();
-
-        if
-        (
-            key == "type"
-         || key == "value"
-         || !dEntry.isStream() || dEntry.stream().empty()
-        )
-        {
-            continue;
-        }
-
-
-        ITstream& is = dEntry.stream();
-
-        // Read first token
-        token firstToken(is);
-
-        if (firstToken.isWord("nonuniform"))
-        {
-            token fieldToken(is);
-
-            if (!fieldToken.isCompound())
-            {
-                if
-                (
-                    fieldToken.isLabel()
-                 && fieldToken.labelToken() == 0
-                )
-                {
-                    scalarFields_.insert(key, autoPtr<scalarField>::New());
-                }
-                else
-                {
-                    FatalIOErrorInFunction(dict)
-                        << "\n    token following 'nonuniform' "
-                           "is not a compound"
-                        << "\n    on patch " << this->patch().name()
-                        << " of field "
-                        << this->internalField().name()
-                        << " in file "
-                        << this->internalField().objectPath() << nl
-                        << exit(FatalIOError);
-                }
-            }
-            else if
-            (
-                fieldToken.compoundToken().type()
-             == token::Compound<List<scalar>>::typeName
-            )
-            {
-                auto fPtr = autoPtr<scalarField>::New();
-
-                fPtr->transfer
-                (
-                    dynamicCast<token::Compound<List<scalar>>>
-                    (
-                        fieldToken.transferCompoundToken(is)
-                    )
-                );
-
-                if (fPtr->size() != patchSize)
-                {
-                    FatalIOErrorInFunction(dict)
-                        << "\n    size of field " << key
-                        << " (" << fPtr->size() << ')'
-                        << " is not the same size as the patch ("
-                        << patchSize << ')'
-                        << "\n    on patch " << this->patch().name()
-                        << " of field "
-                        << this->internalField().name()
-                        << " in file "
-                        << this->internalField().objectPath() << nl
-                        << exit(FatalIOError);
-                }
-
-                scalarFields_.insert(key, fPtr);
-            }
-            else if
-            (
-                fieldToken.compoundToken().type()
-             == token::Compound<List<vector>>::typeName
-            )
-            {
-                auto fPtr = autoPtr<vectorField>::New();
-
-                fPtr->transfer
-                (
-                    dynamicCast<token::Compound<List<vector>>>
-                    (
-                        fieldToken.transferCompoundToken(is)
-                    )
-                );
-
-                if (fPtr->size() != patchSize)
-                {
-                    FatalIOErrorInFunction(dict)
-                        << "\n    size of field " << key
-                        << " (" << fPtr->size() << ')'
-                        << " is not the same size as the patch ("
-                        << patchSize << ')'
-                        << "\n    on patch " << this->patch().name()
-                        << " of field "
-                        << this->internalField().name()
-                        << " in file "
-                        << this->internalField().objectPath() << nl
-                        << exit(FatalIOError);
-                }
-
-                vectorFields_.insert(key, fPtr);
-            }
-            else if
-            (
-                fieldToken.compoundToken().type()
-             == token::Compound<List<sphericalTensor>>::typeName
-            )
-            {
-                auto fPtr = autoPtr<sphericalTensorField>::New();
-
-                fPtr->transfer
-                (
-                    dynamicCast<token::Compound<List<sphericalTensor>>>
-                    (
-                        fieldToken.transferCompoundToken(is)
-                    )
-                );
-
-                if (fPtr->size() != patchSize)
-                {
-                    FatalIOErrorInFunction(dict)
-                        << "\n    size of field " << key
-                        << " (" << fPtr->size() << ')'
-                        << " is not the same size as the patch ("
-                        << patchSize << ')'
-                        << "\n    on patch " << this->patch().name()
-                        << " of field "
-                        << this->internalField().name()
-                        << " in file "
-                        << this->internalField().objectPath() << nl
-                        << exit(FatalIOError);
-                }
-
-                sphTensorFields_.insert(key, fPtr);
-            }
-            else if
-            (
-                fieldToken.compoundToken().type()
-             == token::Compound<List<symmTensor>>::typeName
-            )
-            {
-                auto fPtr = autoPtr<symmTensorField>::New();
-
-                fPtr->transfer
-                (
-                    dynamicCast<token::Compound<List<symmTensor>>>
-                    (
-                        fieldToken.transferCompoundToken(is)
-                    )
-                );
-
-                if (fPtr->size() != patchSize)
-                {
-                    FatalIOErrorInFunction(dict)
-                        << "\n    size of field " << key
-                        << " (" << fPtr->size() << ')'
-                        << " is not the same size as the patch ("
-                        << patchSize << ')'
-                        << "\n    on patch " << this->patch().name()
-                        << " of field "
-                        << this->internalField().name()
-                        << " in file "
-                        << this->internalField().objectPath() << nl
-                        << exit(FatalIOError);
-                }
-
-                symmTensorFields_.insert(key, fPtr);
-            }
-            else if
-            (
-                fieldToken.compoundToken().type()
-             == token::Compound<List<tensor>>::typeName
-            )
-            {
-                auto fPtr = autoPtr<tensorField>::New();
-
-                fPtr->transfer
-                (
-                    dynamicCast<token::Compound<List<tensor>>>
-                    (
-                        fieldToken.transferCompoundToken(is)
-                    )
-                );
-
-                if (fPtr->size() != patchSize)
-                {
-                    FatalIOErrorInFunction(dict)
-                        << "\n    size of field " << key
-                        << " (" << fPtr->size() << ')'
-                        << " is not the same size as the patch ("
-                        << patchSize << ')'
-                        << "\n    on patch " << this->patch().name()
-                        << " of field "
-                        << this->internalField().name()
-                        << " in file "
-                        << this->internalField().objectPath() << nl
-                        << exit(FatalIOError);
-                }
-
-                tensorFields_.insert(key, fPtr);
-            }
-            else
-            {
-                FatalIOErrorInFunction(dict)
-                    << "\n    compound " << fieldToken.compoundToken()
-                    << " not supported"
-                    << "\n    on patch " << this->patch().name()
-                    << " of field "
-                    << this->internalField().name()
-                    << " in file "
-                    << this->internalField().objectPath() << nl
-                    << exit(FatalIOError);
-            }
-        }
-        else if (firstToken.isWord("uniform"))
-        {
-            token fieldToken(is);
-
-            if (!fieldToken.isPunctuation())
-            {
-                scalarFields_.insert
-                (
-                    key,
-                    autoPtr<scalarField>::New
-                    (
-                        patchSize,
-                        fieldToken.number()
-                    )
-                );
-            }
-            else
-            {
-                // Read as scalarList.
-                is.putBack(fieldToken);
-
-                scalarList l(is);
-
-                if (l.size() == vector::nComponents)
-                {
-                    vector vs(l[0], l[1], l[2]);
-
-                    vectorFields_.insert
-                    (
-                        key,
-                        autoPtr<vectorField>::New
-                        (
-                            patchSize,
-                            vs
-                        )
-                    );
-                }
-                else if (l.size() == sphericalTensor::nComponents)
-                {
-                    sphericalTensor vs(l[0]);
-
-                    sphTensorFields_.insert
-                    (
-                        key,
-                        autoPtr<sphericalTensorField>::New
-                        (
-                            patchSize,
-                            vs
-                        )
-                    );
-                }
-                else if (l.size() == symmTensor::nComponents)
-                {
-                    symmTensor vs(l[0], l[1], l[2], l[3], l[4], l[5]);
-
-                    symmTensorFields_.insert
-                    (
-                        key,
-                        autoPtr<symmTensorField>::New
-                        (
-                            patchSize,
-                            vs
-                        )
-                    );
-                }
-                else if (l.size() == tensor::nComponents)
-                {
-                    tensor vs
-                    (
-                        l[0], l[1], l[2],
-                        l[3], l[4], l[5],
-                        l[6], l[7], l[8]
-                    );
-
-                    tensorFields_.insert
-                    (
-                        key,
-                        autoPtr<tensorField>::New
-                        (
-                            patchSize,
-                            vs
-                        )
-                    );
-                }
-                else
-                {
-                    FatalIOErrorInFunction(dict)
-                        << "\n    unrecognised native type " << l
-                        << "\n    on patch " << this->patch().name()
-                        << " of field "
-                        << this->internalField().name()
-                        << " in file "
-                        << this->internalField().objectPath() << nl
-                        << exit(FatalIOError);
-                }
-            }
-        }
-    }
+    // Handle "value" separately
+    processGeneric(patchSize, patchName, io, true);
 }
 
 
 template<class Type>
 Foam::genericFvsPatchField<Type>::genericFvsPatchField
 (
-    const genericFvsPatchField<Type>& ptf,
+    const genericFvsPatchField<Type>& rhs,
     const fvPatch& p,
     const DimensionedField<Type, surfaceMesh>& iF,
     const fvPatchFieldMapper& mapper
 )
 :
-    calculatedFvsPatchField<Type>(ptf, p, iF, mapper),
-    actualTypeName_(ptf.actualTypeName_),
-    dict_(ptf.dict_)
+    parent_bctype(rhs, p, iF, mapper),
+    genericPatchFieldBase(zero{}, rhs)
 {
-    forAllConstIters(ptf.scalarFields_, iter)
-    {
-        scalarFields_.insert
-        (
-            iter.key(),
-            autoPtr<scalarField>::New(*iter(), mapper)
-        );
-    }
-
-    forAllConstIters(ptf.vectorFields_, iter)
-    {
-        vectorFields_.insert
-        (
-            iter.key(),
-            autoPtr<vectorField>::New(*iter(), mapper)
-        );
-    }
-
-    forAllConstIters(ptf.sphTensorFields_, iter)
-    {
-        sphTensorFields_.insert
-        (
-            iter.key(),
-            autoPtr<sphericalTensorField>::New(*iter(), mapper)
-        );
-    }
-
-    forAllConstIters(ptf.symmTensorFields_, iter)
-    {
-        symmTensorFields_.insert
-        (
-            iter.key(),
-            autoPtr<symmTensorField>::New(*iter(), mapper)
-        );
-    }
-
-    forAllConstIters(ptf.tensorFields_, iter)
-    {
-        tensorFields_.insert
-        (
-            iter.key(),
-            autoPtr<tensorField>::New(*iter(), mapper)
-        );
-    }
+    this->mapGeneric(rhs, mapper);
 }
 
 
 template<class Type>
 Foam::genericFvsPatchField<Type>::genericFvsPatchField
 (
-    const genericFvsPatchField<Type>& ptf
-)
-:
-    calculatedFvsPatchField<Type>(ptf),
-    actualTypeName_(ptf.actualTypeName_),
-    dict_(ptf.dict_),
-    scalarFields_(ptf.scalarFields_),
-    vectorFields_(ptf.vectorFields_),
-    sphTensorFields_(ptf.sphTensorFields_),
-    symmTensorFields_(ptf.symmTensorFields_),
-    tensorFields_(ptf.tensorFields_)
-{}
-
-
-template<class Type>
-Foam::genericFvsPatchField<Type>::genericFvsPatchField
-(
-    const genericFvsPatchField<Type>& ptf,
+    const genericFvsPatchField<Type>& rhs,
     const DimensionedField<Type, surfaceMesh>& iF
 )
 :
-    calculatedFvsPatchField<Type>(ptf, iF),
-    actualTypeName_(ptf.actualTypeName_),
-    dict_(ptf.dict_),
-    scalarFields_(ptf.scalarFields_),
-    vectorFields_(ptf.vectorFields_),
-    sphTensorFields_(ptf.sphTensorFields_),
-    symmTensorFields_(ptf.symmTensorFields_),
-    tensorFields_(ptf.tensorFields_)
+    parent_bctype(rhs, iF),
+    genericPatchFieldBase(rhs)
 {}
 
 
 // * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
 
+
+template<class Type>
+void Foam::genericFvsPatchField<Type>::write(Ostream& os) const
+{
+    // Handle "value" separately
+    genericPatchFieldBase::writeGeneric(os, true);
+    this->writeEntry("value", os);
+}
+
+
 template<class Type>
 void Foam::genericFvsPatchField<Type>::autoMap
 (
     const fvPatchFieldMapper& m
 )
 {
-    calculatedFvsPatchField<Type>::autoMap(m);
-
-    forAllIters(scalarFields_, iter)
-    {
-        (*iter)->autoMap(m);
-    }
-
-    forAllIters(vectorFields_, iter)
-    {
-        (*iter)->autoMap(m);
-    }
-
-    forAllIters(sphTensorFields_, iter)
-    {
-        (*iter)->autoMap(m);
-    }
-
-    forAllIters(symmTensorFields_, iter)
-    {
-        (*iter)->autoMap(m);
-    }
-
-    forAllIters(tensorFields_, iter)
-    {
-        (*iter)->autoMap(m);
-    }
+    parent_bctype::autoMap(m);
+    this->autoMapGeneric(m);
 }
 
 
 template<class Type>
 void Foam::genericFvsPatchField<Type>::rmap
 (
-    const fvsPatchField<Type>& ptf,
+    const fvsPatchField<Type>& rhs,
     const labelList& addr
 )
 {
-    calculatedFvsPatchField<Type>::rmap(ptf, addr);
+    parent_bctype::rmap(rhs, addr);
 
-    const genericFvsPatchField<Type>& dptf =
-        refCast<const genericFvsPatchField<Type>>(ptf);
-
-    forAllIters(scalarFields_, iter)
-    {
-        const auto iter2 = dptf.scalarFields_.cfind(iter.key());
-
-        if (iter2.found())
-        {
-            (*iter)->rmap(*iter2(), addr);
-        }
-    }
-
-    forAllIters(vectorFields_, iter)
-    {
-        const auto iter2 = dptf.vectorFields_.find(iter.key());
-
-        if (iter2.found())
-        {
-            (*iter)->rmap(*iter2(), addr);
-        }
-    }
-
-    forAllIters(sphTensorFields_, iter)
+    const auto* base = isA<genericPatchFieldBase>(rhs);
+    if (base)
     {
-        const auto iter2 = dptf.sphTensorFields_.find(iter.key());
-
-        if (iter2.found())
-        {
-            (*iter)->rmap(*iter2(), addr);
-        }
-    }
-
-    forAllIters(symmTensorFields_, iter)
-    {
-        const auto iter2 = dptf.symmTensorFields_.find(iter.key());
-
-        if (iter2.found())
-        {
-            (*iter)->rmap(*iter2(), addr);
-        }
-    }
-
-    forAllIters(tensorFields_, iter)
-    {
-        const auto iter2 = dptf.tensorFields_.find(iter.key());
-
-        if (iter2.found())
-        {
-            (*iter)->rmap(*iter2(), addr);
-        }
+        this->rmapGeneric(*base, addr);
     }
 }
 
@@ -606,14 +148,14 @@ Foam::genericFvsPatchField<Type>::valueInternalCoeffs
 ) const
 {
     FatalErrorInFunction
-        << "cannot be called for a genericFvsPatchField"
-           " (actual type " << actualTypeName_ << ")"
-        << "\n    on patch " << this->patch().name()
-        << " of field " << this->internalField().name()
-        << " in file " << this->internalField().objectPath()
-        << "\n    You are probably trying to solve for a field with a "
-           "generic boundary condition."
-        << abort(FatalError);
+        << "Cannot be called for a genericFvsPatchField";
+
+    genericFatalSolveError
+    (
+        this->patch().name(),
+        this->internalField()
+    );
+    FatalError << abort(FatalError);
 
     return *this;
 }
@@ -627,14 +169,14 @@ Foam::genericFvsPatchField<Type>::valueBoundaryCoeffs
 ) const
 {
     FatalErrorInFunction
-        << "cannot be called for a genericFvsPatchField"
-           " (actual type " << actualTypeName_ << ")"
-        << "\n    on patch " << this->patch().name()
-        << " of field " << this->internalField().name()
-        << " in file " << this->internalField().objectPath()
-        << "\n    You are probably trying to solve for a field with a "
-           "generic boundary condition."
-        << abort(FatalError);
+        << "Cannot be called for a genericFvsPatchField";
+
+    genericFatalSolveError
+    (
+        this->patch().name(),
+        this->internalField()
+    );
+    FatalError << abort(FatalError);
 
     return *this;
 }
@@ -645,14 +187,14 @@ Foam::tmp<Foam::Field<Type>>
 Foam::genericFvsPatchField<Type>::gradientInternalCoeffs() const
 {
     FatalErrorInFunction
-        << "cannot be called for a genericFvsPatchField"
-           " (actual type " << actualTypeName_ << ")"
-        << "\n    on patch " << this->patch().name()
-        << " of field " << this->internalField().name()
-        << " in file " << this->internalField().objectPath()
-        << "\n    You are probably trying to solve for a field with a "
-           "generic boundary condition."
-        << abort(FatalError);
+        << "Cannot be called for a genericFvsPatchField";
+
+    genericFatalSolveError
+    (
+        this->patch().name(),
+        this->internalField()
+    );
+    FatalError << abort(FatalError);
 
     return *this;
 }
@@ -662,75 +204,16 @@ Foam::tmp<Foam::Field<Type>>
 Foam::genericFvsPatchField<Type>::gradientBoundaryCoeffs() const
 {
     FatalErrorInFunction
-        << "cannot be called for a genericFvsPatchField"
-           " (actual type " << actualTypeName_ << ")"
-        << "\n    on patch " << this->patch().name()
-        << " of field " << this->internalField().name()
-        << " in file " << this->internalField().objectPath()
-        << "\n    You are probably trying to solve for a field with a "
-           "generic boundary condition."
-        << abort(FatalError);
-
-    return *this;
-}
+        << "Cannot be called for a genericFvsPatchField";
 
+    genericFatalSolveError
+    (
+        this->patch().name(),
+        this->internalField()
+    );
+    FatalError << abort(FatalError);
 
-template<class Type>
-const Foam::word& Foam::genericFvsPatchField<Type>::actualType() const
-{
-    return actualTypeName_;
-}
-
-
-template<class Type>
-void Foam::genericFvsPatchField<Type>::write(Ostream& os) const
-{
-    os.writeEntry("type", actualTypeName_);
-
-    for (const entry& dEntry : dict_)
-    {
-        const keyType& key = dEntry.keyword();
-
-        if (key == "type" || key == "value")
-        {
-            // NB: "type" written first, "value" written last
-            continue;
-        }
-        else if
-        (
-            dEntry.isStream()
-         && dEntry.stream().size()
-         && dEntry.stream()[0].isWord("nonuniform")
-        )
-        {
-            if (scalarFields_.found(key))
-            {
-                scalarFields_.cfind(key)()->writeEntry(key, os);
-            }
-            else if (vectorFields_.found(key))
-            {
-                vectorFields_.cfind(key)()->writeEntry(key, os);
-            }
-            else if (sphTensorFields_.found(key))
-            {
-                sphTensorFields_.cfind(key)()->writeEntry(key, os);
-            }
-            else if (symmTensorFields_.found(key))
-            {
-                symmTensorFields_.cfind(key)()->writeEntry(key, os);
-            }
-            else if (tensorFields_.found(key))
-            {
-                tensorFields_.cfind(key)()->writeEntry(key, os);
-            }
-        }
-        else
-        {
-            dEntry.write(os);
-        }
-    }
-
-    this->writeEntry("value", os);
+    return *this;
 }
 
 
diff --git a/src/genericPatchFields/genericFvsPatchField/genericFvsPatchField.H b/src/genericPatchFields/genericFvsPatchField/genericFvsPatchField.H
index b5d3b942f87126e04a9b85b259960622fcf96986..3fe2a26c57e989661c1c0e8d69a2faa727516dc8 100644
--- a/src/genericPatchFields/genericFvsPatchField/genericFvsPatchField.H
+++ b/src/genericPatchFields/genericFvsPatchField/genericFvsPatchField.H
@@ -5,7 +5,7 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2019 OpenCFD Ltd.
+    Copyright (C) 2019-2021 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -43,7 +43,7 @@ SourceFiles
 #define genericFvsPatchField_H
 
 #include "calculatedFvsPatchField.H"
-#include "HashPtrTable.H"
+#include "genericPatchFieldBase.H"
 
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
 
@@ -57,19 +57,11 @@ namespace Foam
 template<class Type>
 class genericFvsPatchField
 :
-    public calculatedFvsPatchField<Type>
+    public calculatedFvsPatchField<Type>,
+    public genericPatchFieldBase
 {
-    // Private Data
-
-        const word actualTypeName_;
-
-        dictionary dict_;
-
-        HashPtrTable<scalarField> scalarFields_;
-        HashPtrTable<vectorField> vectorFields_;
-        HashPtrTable<sphericalTensorField> sphTensorFields_;
-        HashPtrTable<symmTensorField> symmTensorFields_;
-        HashPtrTable<tensorField> tensorFields_;
+    //- The parent boundary condition type
+    typedef calculatedFvsPatchField<Type> parent_bctype;
 
 
 public:
@@ -104,12 +96,6 @@ public:
             const fvPatchFieldMapper&
         );
 
-        //- Construct as copy
-        genericFvsPatchField
-        (
-            const genericFvsPatchField<Type>&
-        );
-
         //- Construct and return a clone
         virtual tmp<fvsPatchField<Type>> clone() const
         {
@@ -119,6 +105,9 @@ public:
             );
         }
 
+        //- Default copy construct
+        genericFvsPatchField(const genericFvsPatchField<Type>&) = default;
+
         //- Construct as copy setting internal field reference
         genericFvsPatchField
         (
@@ -141,52 +130,38 @@ public:
 
     // Member Functions
 
-        // Mapping Functions
-
-            //- Map (and resize as needed) from self given a mapping object
-            virtual void autoMap
-            (
-                const fvPatchFieldMapper&
-            );
+        //- Write
+        virtual void write(Ostream&) const;
 
-            //- Reverse map the given fvsPatchField onto this fvsPatchField
-            virtual void rmap
-            (
-                const fvsPatchField<Type>&,
-                const labelList&
-            );
 
+    // Mapping Functions
 
-        // Evaluation Functions
+        //- Map (and resize as needed) from self given a mapping object
+        virtual void autoMap(const fvPatchFieldMapper&);
 
-            //- Return the matrix diagonal coefficients corresponding to the
-            //  evaluation of the value of this patchField with given weights
-            virtual tmp<Field<Type>> valueInternalCoeffs
-            (
-                const tmp<scalarField>&
-            ) const;
+        //- Reverse map the given faPatchField onto this faPatchField
+        virtual void rmap
+        (
+            const fvsPatchField<Type>&,
+            const labelList&
+        );
 
-            //- Return the matrix source coefficients corresponding to the
-            //  evaluation of the value of this patchField with given weights
-            virtual tmp<Field<Type>> valueBoundaryCoeffs
-            (
-                const tmp<scalarField>&
-            ) const;
 
-            //- Return the matrix diagonal coefficients corresponding to the
-            //  evaluation of the gradient of this patchField
-            tmp<Field<Type>> gradientInternalCoeffs() const;
+    // Evaluation Functions
 
-            //- Return the matrix source coefficients corresponding to the
-            //  evaluation of the gradient of this patchField
-            tmp<Field<Type>> gradientBoundaryCoeffs() const;
+        //- Fatal
+        virtual tmp<Field<Type>>
+        valueInternalCoeffs(const tmp<scalarField>&) const;
 
+        //- Fatal
+        virtual tmp<Field<Type>>
+        valueBoundaryCoeffs(const tmp<scalarField>&) const;
 
-        //- Return the actual type
-        const word& actualType() const;
+        //- Fatal
+        tmp<Field<Type>> gradientInternalCoeffs() const;
 
-        //- Write
-        virtual void write(Ostream&) const;
+        //- Fatal
+        tmp<Field<Type>> gradientBoundaryCoeffs() const;
 };
 
 
diff --git a/src/genericPatchFields/genericPatchFieldBase/genericPatchFieldBase.C b/src/genericPatchFields/genericPatchFieldBase/genericPatchFieldBase.C
new file mode 100644
index 0000000000000000000000000000000000000000..5108533f98860cdcfa1edf885a37b518cd74c0e5
--- /dev/null
+++ b/src/genericPatchFields/genericPatchFieldBase/genericPatchFieldBase.C
@@ -0,0 +1,547 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | www.openfoam.com
+     \\/     M anipulation  |
+-------------------------------------------------------------------------------
+    Copyright (C) 2021 OpenCFD Ltd.
+-------------------------------------------------------------------------------
+License
+    This file is part of OpenFOAM.
+
+    OpenFOAM is free software: you can redistribute it and/or modify it
+    under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    OpenFOAM is distributed in the hope that it will be useful, but WITHOUT
+    ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+    FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+    for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with OpenFOAM.  If not, see <http://www.gnu.org/licenses/>.
+
+\*---------------------------------------------------------------------------*/
+
+#include "genericPatchFieldBase.H"
+#include "messageStream.H"
+
+// * * * * * * * * * * * * * Private Member Functions  * * * * * * * * * * * //
+
+bool Foam::genericPatchFieldBase::checkFieldSize
+(
+    const label fieldSize,
+    const label patchSize,
+    const word& patchName,
+    const keyType& key,
+    const IOobject& io
+) const
+{
+    const bool ok = (fieldSize == patchSize);
+
+    if (!ok)
+    {
+        FatalIOErrorInFunction(dict_)
+            << "\n    size of field " << key
+            << " (" << fieldSize << ") != patch size (" << patchSize << ')'
+            << "\n    on patch " << patchName
+            << " of field " << io.name() << " in file "
+            << io.objectPath() << nl
+            << exit(FatalIOError);
+    }
+
+    return ok;
+}
+
+
+// * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
+
+Foam::genericPatchFieldBase::genericPatchFieldBase
+(
+    const dictionary& dict
+)
+:
+    actualTypeName_(dict.get<word>("type")),
+    dict_(dict)
+{}
+
+
+Foam::genericPatchFieldBase::genericPatchFieldBase
+(
+    const Foam::zero,
+    const genericPatchFieldBase& rhs
+)
+:
+    actualTypeName_(rhs.actualTypeName_),
+    dict_(rhs.dict_)
+{}
+
+
+// * * * * * * * * * * * * Protected Member Functions  * * * * * * * * * * * //
+
+void Foam::genericPatchFieldBase::genericFatalSolveError
+(
+    const word& patchName,
+    const IOobject& io
+) const
+{
+    FatalError
+        << " (actual type " << actualTypeName_ << ')'
+        << "\n    on patch " << patchName
+        << " of field " << io.name() << " in file " << io.objectPath() << nl
+        << nl
+        << "    You are probably trying to solve for a field with a "
+           "generic boundary condition." << nl;
+}
+
+
+void Foam::genericPatchFieldBase::reportMissingEntry
+(
+    const word& entryName,
+    const word& patchName,
+    const IOobject& io
+) const
+{
+    FatalIOErrorInFunction(dict_)
+        << nl
+        << "    Missing required '" << entryName << "' entry"
+        << " on patch " << patchName
+        << " of field " << io.name() << " in file " << io.objectPath() << nl
+        << "    (Actual type " << actualTypeName_ << ')' << nl << nl
+        << "    Please add the '" << entryName << "' entry to the"
+           " write function of the user-defined boundary-condition" << nl
+        << exit(FatalIOError);
+}
+
+
+void Foam::genericPatchFieldBase::processGeneric
+(
+    const label patchSize,
+    const word& patchName,
+    const IOobject& io,
+    const bool separateValue
+)
+{
+    for (const entry& dEntry : dict_)
+    {
+        const keyType& key = dEntry.keyword();
+
+        if (key == "type" || (separateValue && key == "value"))
+        {
+            // "type" and possibly "value" handled differently
+        }
+        else
+        {
+            processEntry(dEntry, patchSize, patchName, io);
+        }
+    }
+}
+
+
+bool Foam::genericPatchFieldBase::processEntry
+(
+    const entry& dEntry,
+    const label patchSize,
+    const word& patchName,
+    const IOobject& io
+)
+{
+    if (!dEntry.isStream())
+    {
+        return false;
+    }
+
+    const keyType& key = dEntry.keyword();
+    ITstream& is = dEntry.stream();
+
+    if (is.empty())
+    {
+        return false;
+    }
+
+    #undef FIELDSIZE_CHECK
+    #define FIELDSIZE_CHECK(fieldLen)  \
+        checkFieldSize(fieldLen, patchSize, patchName, key, io)
+
+
+    // First token
+    token tok(is);
+
+    if (tok.isWord("nonuniform"))
+    {
+        is >> tok;
+
+        if (tok.isLabel(0))
+        {
+            // For v2006 and earlier, could have a plain untyped 0
+            // without a compound type.
+            // Just treat as scalar and hope for the best.
+            scalarFields_.insert(key, autoPtr<scalarField>::New());
+        }
+        else if (!tok.isCompound())
+        {
+            FatalIOErrorInFunction(dict_)
+                << "\n    non-compound token following 'nonuniform'"
+                << "\n    on patch " << patchName << " field "
+                << io.name() << " in file "
+                << io.objectPath() << nl
+                << exit(FatalIOError);
+            return false;
+        }
+        else if
+        (
+            tok.compoundToken().type()
+         == token::Compound<List<scalar>>::typeName
+        )
+        {
+            auto fPtr = autoPtr<scalarField>::New();
+
+            fPtr->transfer
+            (
+                dynamicCast<token::Compound<List<scalar>>>
+                (
+                    tok.transferCompoundToken(is)
+                )
+            );
+
+            if (!FIELDSIZE_CHECK(fPtr->size()))
+            {
+                return false;
+            }
+
+            scalarFields_.insert(key, fPtr);
+        }
+        else if
+        (
+            tok.compoundToken().type()
+         == token::Compound<List<vector>>::typeName
+        )
+        {
+            auto fPtr = autoPtr<vectorField>::New();
+
+            fPtr->transfer
+            (
+                dynamicCast<token::Compound<List<vector>>>
+                (
+                    tok.transferCompoundToken(is)
+                )
+            );
+
+            if (!FIELDSIZE_CHECK(fPtr->size()))
+            {
+                return false;
+            }
+            vectorFields_.insert(key, fPtr);
+        }
+        else if
+        (
+            tok.compoundToken().type()
+         == token::Compound<List<sphericalTensor>>::typeName
+        )
+        {
+            auto fPtr = autoPtr<sphericalTensorField>::New();
+
+            fPtr->transfer
+            (
+                dynamicCast<token::Compound<List<sphericalTensor>>>
+                (
+                    tok.transferCompoundToken(is)
+                )
+            );
+
+            if (!FIELDSIZE_CHECK(fPtr->size()))
+            {
+                return false;
+            }
+
+            sphTensorFields_.insert(key, fPtr);
+        }
+        else if
+        (
+            tok.compoundToken().type()
+         == token::Compound<List<symmTensor>>::typeName
+        )
+        {
+            auto fPtr = autoPtr<symmTensorField>::New();
+
+            fPtr->transfer
+            (
+                dynamicCast<token::Compound<List<symmTensor>>>
+                (
+                    tok.transferCompoundToken(is)
+                )
+            );
+
+            if (!FIELDSIZE_CHECK(fPtr->size()))
+            {
+                return false;
+            }
+
+            symmTensorFields_.insert(key, fPtr);
+        }
+        else if
+        (
+            tok.compoundToken().type()
+         == token::Compound<List<tensor>>::typeName
+        )
+        {
+            auto fPtr = autoPtr<tensorField>::New();
+
+            fPtr->transfer
+            (
+                dynamicCast<token::Compound<List<tensor>>>
+                (
+                    tok.transferCompoundToken(is)
+                )
+            );
+
+            if (!FIELDSIZE_CHECK(fPtr->size()))
+            {
+                return false;
+            }
+
+            tensorFields_.insert(key, fPtr);
+        }
+        else
+        {
+            FatalIOErrorInFunction(dict_)
+                << "\n    unsupported compound " << tok.compoundToken()
+                << "\n    on patch " << patchName << " of field "
+                << io.name() << " in file "
+                << io.objectPath() << nl
+                << exit(FatalIOError);
+            return false;
+        }
+    }
+    else if (tok.isWord("uniform"))
+    {
+        is >> tok;
+
+        if (!tok.isPunctuation())
+        {
+            scalarFields_.insert
+            (
+                key,
+                autoPtr<scalarField>::New(patchSize, tok.number())
+            );
+        }
+        else
+        {
+            // Read vector-space as list of scalars
+            is.putBack(tok);
+
+            scalarList list(is);
+
+            if (list.size() == vector::nComponents)
+            {
+                vector vs(list[0], list[1], list[2]);
+
+                vectorFields_.insert
+                (
+                    key,
+                    autoPtr<vectorField>::New
+                    (
+                        patchSize,
+                        vs
+                    )
+                );
+            }
+            else if (list.size() == sphericalTensor::nComponents)
+            {
+                sphericalTensor vs(list[0]);
+
+                sphTensorFields_.insert
+                (
+                    key,
+                    autoPtr<sphericalTensorField>::New
+                    (
+                        patchSize,
+                        vs
+                    )
+                );
+            }
+            else if (list.size() == symmTensor::nComponents)
+            {
+                symmTensor vs
+                (
+                    list[0], list[1], list[2],
+                    list[3], list[4],
+                    list[5]
+                );
+
+                symmTensorFields_.insert
+                (
+                    key,
+                    autoPtr<symmTensorField>::New
+                    (
+                        patchSize,
+                        vs
+                    )
+                );
+            }
+            else if (list.size() == tensor::nComponents)
+            {
+                tensor vs
+                (
+                    list[0], list[1], list[2],
+                    list[3], list[4], list[5],
+                    list[6], list[7], list[8]
+                );
+
+                tensorFields_.insert
+                (
+                    key,
+                    autoPtr<tensorField>::New
+                    (
+                        patchSize,
+                        vs
+                    )
+                );
+            }
+            else
+            {
+                FatalIOErrorInFunction(dict_)
+                    << "\n    unrecognised native type " << flatOutput(list)
+                    << "\n    on patch " << patchName << " of field "
+                    << io.name() << " in file "
+                    << io.objectPath() << nl
+                    << exit(FatalIOError);
+                return false;
+            }
+        }
+    }
+
+    #undef FIELDSIZE_CHECK
+
+    return true;
+}
+
+
+void Foam::genericPatchFieldBase::putEntry
+(
+    const entry& e,
+    Ostream& os
+) const
+{
+    const keyType& key = e.keyword();
+
+    if
+    (
+        e.isStream()
+     && e.stream().size()
+     && e.stream()[0].isWord("nonuniform")
+    )
+    {
+        if (scalarFields_.found(key))
+        {
+            scalarFields_.cfind(key)()->writeEntry(key, os);
+        }
+        else if (vectorFields_.found(key))
+        {
+            vectorFields_.cfind(key)()->writeEntry(key, os);
+        }
+        else if (sphTensorFields_.found(key))
+        {
+            sphTensorFields_.cfind(key)()->writeEntry(key, os);
+        }
+        else if (symmTensorFields_.found(key))
+        {
+            symmTensorFields_.cfind(key)()->writeEntry(key, os);
+        }
+        else if (tensorFields_.found(key))
+        {
+            tensorFields_.cfind(key)()->writeEntry(key, os);
+        }
+    }
+    else
+    {
+        e.write(os);
+    }
+}
+
+
+void Foam::genericPatchFieldBase::writeGeneric
+(
+    Ostream& os,
+    const bool separateValue
+) const
+{
+    os.writeEntry("type", actualTypeName_);
+
+    for (const entry& dEntry : dict_)
+    {
+        const keyType& key = dEntry.keyword();
+
+        if (key == "type" || (separateValue && key == "value"))
+        {
+            // NB: "type" written first, "value" possibly separately
+        }
+        else
+        {
+            putEntry(dEntry, os);
+        }
+    }
+}
+
+
+void Foam::genericPatchFieldBase::rmapGeneric
+(
+    const genericPatchFieldBase& rhs,
+    const labelList& addr
+)
+{
+    forAllIters(scalarFields_, iter)
+    {
+        const auto iter2 = rhs.scalarFields_.cfind(iter.key());
+
+        if (iter2.found())
+        {
+            (*iter)->rmap(*iter2(), addr);
+        }
+    }
+
+    forAllIters(vectorFields_, iter)
+    {
+        const auto iter2 = rhs.vectorFields_.cfind(iter.key());
+
+        if (iter2.found())
+        {
+            (*iter)->rmap(*iter2(), addr);
+        }
+    }
+
+    forAllIters(sphTensorFields_, iter)
+    {
+        const auto iter2 = rhs.sphTensorFields_.cfind(iter.key());
+
+        if (iter2.found())
+        {
+            (*iter)->rmap(*iter2(), addr);
+        }
+    }
+
+    forAllIters(symmTensorFields_, iter)
+    {
+        const auto iter2 = rhs.symmTensorFields_.cfind(iter.key());
+
+        if (iter2.found())
+        {
+            (*iter)->rmap(*iter2(), addr);
+        }
+    }
+
+    forAllIters(tensorFields_, iter)
+    {
+        const auto iter2 = rhs.tensorFields_.find(iter.key());
+
+        if (iter2.found())
+        {
+            (*iter)->rmap(*iter2(), addr);
+        }
+    }
+}
+
+
+// ************************************************************************* //
diff --git a/src/genericPatchFields/genericPatchFieldBase/genericPatchFieldBase.H b/src/genericPatchFields/genericPatchFieldBase/genericPatchFieldBase.H
new file mode 100644
index 0000000000000000000000000000000000000000..f22346c7de1b71e32d258d3c3f1498f5798a23b8
--- /dev/null
+++ b/src/genericPatchFields/genericPatchFieldBase/genericPatchFieldBase.H
@@ -0,0 +1,194 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | www.openfoam.com
+     \\/     M anipulation  |
+-------------------------------------------------------------------------------
+    Copyright (C) 2021 OpenCFD Ltd.
+-------------------------------------------------------------------------------
+License
+    This file is part of OpenFOAM.
+
+    OpenFOAM is free software: you can redistribute it and/or modify it
+    under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    OpenFOAM is distributed in the hope that it will be useful, but WITHOUT
+    ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+    FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+    for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with OpenFOAM.  If not, see <http://www.gnu.org/licenses/>.
+
+Class
+    Foam::genericPatchFieldBase
+
+Description
+    Generic infrastructure for reading/writing unknown patch types.
+
+SourceFiles
+    genericPatchFieldBase.C
+    genericPatchFieldBaseTemplates.C
+
+\*---------------------------------------------------------------------------*/
+
+#ifndef genericPatchFieldBase_H
+#define genericPatchFieldBase_H
+
+#include "dictionary.H"
+#include "primitiveFields.H"
+#include "HashPtrTable.H"
+#include "IOobject.H"
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+namespace Foam
+{
+
+/*---------------------------------------------------------------------------*\
+                    Class genericPatchFieldBase Declaration
+\*---------------------------------------------------------------------------*/
+
+class genericPatchFieldBase
+{
+    // Private Member Functions
+
+        bool checkFieldSize
+        (
+            const label fieldSize,
+            const label patchSize,
+            const word& patchName,
+            const keyType& key,
+            const IOobject& io
+        ) const;
+
+
+protected:
+
+    // Protected Data
+
+        //- The non-generic patch name
+        word actualTypeName_;
+
+        dictionary dict_;
+
+        HashPtrTable<scalarField> scalarFields_;
+        HashPtrTable<vectorField> vectorFields_;
+        HashPtrTable<sphericalTensorField> sphTensorFields_;
+        HashPtrTable<symmTensorField> symmTensorFields_;
+        HashPtrTable<tensorField> tensorFields_;
+
+
+    // Protected Member Functions
+
+        //- Add error message to FatalError about solving with
+        //- generic condition
+        void genericFatalSolveError
+        (
+            const word& patchName,
+            const IOobject& io
+        ) const;
+
+        //- FatalError for missing entry
+        void reportMissingEntry
+        (
+            const word& entryName,
+            const word& patchName,
+            const IOobject& io
+        ) const;
+
+        void processGeneric
+        (
+            const label patchSize,
+            const word& patchName,
+            const IOobject& io,
+            const bool separateValue
+        );
+
+        bool processEntry
+        (
+            const entry& dEntry,
+            const label patchSize,
+            const word& patchName,
+            const IOobject& io
+        );
+
+        //- Write a single entry, with lookup of hashed values
+        void putEntry(const entry& e, Ostream& os) const;
+
+        //- Write all generic entries from dictionary,
+        //- optionally treating the "value" entry separately
+        void writeGeneric(Ostream& os, const bool separateValue) const;
+
+        //- Implementation for construct with mapper
+        template<class MapperType>
+        void mapGeneric
+        (
+            const genericPatchFieldBase& rhs,
+            const MapperType& mapper
+        );
+
+        //- Implementation for autoMap of self given a mapping object
+        template<class MapperType>
+        void autoMapGeneric(const MapperType& mapper);
+
+        //- Implementation for reverse map given patch field onto this
+        //- patch field
+        void rmapGeneric
+        (
+            const genericPatchFieldBase& rhs,
+            const labelList& addr
+        );
+
+
+    // Constructors
+
+        //- Partial copy construct. Only copy type and dictionary
+        genericPatchFieldBase(const Foam::zero, const genericPatchFieldBase&);
+
+
+public:
+
+    // Constructors
+
+        //- Default construct, generally not useful.
+        genericPatchFieldBase() = default;
+
+        //- Copy construct
+        genericPatchFieldBase(const genericPatchFieldBase&) = default;
+
+        //- Move construct
+        genericPatchFieldBase(genericPatchFieldBase&&) = default;
+
+        //- Initialize from dictionary
+        explicit genericPatchFieldBase(const dictionary& dict);
+
+
+    // Member Functions
+
+        //- The actual patch type
+        const word& actualType() const noexcept
+        {
+            return actualTypeName_;
+        }
+};
+
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+} // End namespace Foam
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+#ifdef NoRepository
+    #include "genericPatchFieldBaseTemplates.C"
+#endif
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+#endif
+
+// ************************************************************************* //
diff --git a/src/genericPatchFields/genericPatchFieldBase/genericPatchFieldBaseTemplates.C b/src/genericPatchFields/genericPatchFieldBase/genericPatchFieldBaseTemplates.C
new file mode 100644
index 0000000000000000000000000000000000000000..9b1041ab456f71726428ea16daca5b24f07714f9
--- /dev/null
+++ b/src/genericPatchFields/genericPatchFieldBase/genericPatchFieldBaseTemplates.C
@@ -0,0 +1,119 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | www.openfoam.com
+     \\/     M anipulation  |
+-------------------------------------------------------------------------------
+    Copyright (C) 2021 OpenCFD Ltd.
+-------------------------------------------------------------------------------
+License
+    This file is part of OpenFOAM.
+
+    OpenFOAM is free software: you can redistribute it and/or modify it
+    under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    OpenFOAM is distributed in the hope that it will be useful, but WITHOUT
+    ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+    FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+    for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with OpenFOAM.  If not, see <http://www.gnu.org/licenses/>.
+
+\*---------------------------------------------------------------------------*/
+
+#include "genericPatchFieldBase.H"
+
+// * * * * * * * * * * * * Protected Member Functions  * * * * * * * * * * * //
+
+template<class MapperType>
+void Foam::genericPatchFieldBase::mapGeneric
+(
+    const genericPatchFieldBase& rhs,
+    const MapperType& mapper
+)
+{
+    forAllConstIters(rhs.scalarFields_, iter)
+    {
+        scalarFields_.insert
+        (
+            iter.key(),
+            autoPtr<scalarField>::New(*iter(), mapper)
+        );
+    }
+
+    forAllConstIters(rhs.vectorFields_, iter)
+    {
+        vectorFields_.insert
+        (
+            iter.key(),
+            autoPtr<vectorField>::New(*iter(), mapper)
+        );
+    }
+
+    forAllConstIters(rhs.sphTensorFields_, iter)
+    {
+        sphTensorFields_.insert
+        (
+            iter.key(),
+            autoPtr<sphericalTensorField>::New(*iter(), mapper)
+        );
+    }
+
+    forAllConstIters(rhs.symmTensorFields_, iter)
+    {
+        symmTensorFields_.insert
+        (
+            iter.key(),
+            autoPtr<symmTensorField>::New(*iter(), mapper)
+        );
+    }
+
+    forAllConstIters(rhs.tensorFields_, iter)
+    {
+        tensorFields_.insert
+        (
+            iter.key(),
+            autoPtr<tensorField>::New(*iter(), mapper)
+        );
+    }
+}
+
+
+template<class MapperType>
+void Foam::genericPatchFieldBase::autoMapGeneric
+(
+    const MapperType& mapper
+)
+{
+    forAllIters(scalarFields_, iter)
+    {
+        (*iter)->autoMap(mapper);
+    }
+
+    forAllIters(vectorFields_, iter)
+    {
+        (*iter)->autoMap(mapper);
+    }
+
+    forAllIters(sphTensorFields_, iter)
+    {
+        (*iter)->autoMap(mapper);
+    }
+
+    forAllIters(symmTensorFields_, iter)
+    {
+        (*iter)->autoMap(mapper);
+    }
+
+    forAllIters(tensorFields_, iter)
+    {
+        (*iter)->autoMap(mapper);
+    }
+}
+
+
+// ************************************************************************* //
diff --git a/src/genericPatchFields/genericPointPatchField/genericPointPatchField.C b/src/genericPatchFields/genericPointPatchField/genericPointPatchField.C
index a4ccaecb18916040a32b275addc51320058284b0..7352d82c5cdc4e78f00cea06c134726592f10ffe 100644
--- a/src/genericPatchFields/genericPointPatchField/genericPointPatchField.C
+++ b/src/genericPatchFields/genericPointPatchField/genericPointPatchField.C
@@ -38,9 +38,13 @@ Foam::genericPointPatchField<Type>::genericPointPatchField
     const DimensionedField<Type, pointMesh>& iF
 )
 :
-    calculatedPointPatchField<Type>(p, iF)
+    parent_bctype(p, iF)
 {
-    NotImplemented;
+    FatalErrorInFunction
+        << "Trying to construct genericPointPatchField on patch "
+        << this->patch().name()
+        << " of field " << this->internalField().name() << nl
+        << abort(FatalError);
 }
 
 
@@ -52,468 +56,77 @@ Foam::genericPointPatchField<Type>::genericPointPatchField
     const dictionary& dict
 )
 :
-    calculatedPointPatchField<Type>(p, iF, dict),
-    actualTypeName_(dict.get<word>("type")),
-    dict_(dict)
+    parent_bctype(p, iF, dict),
+    genericPatchFieldBase(dict)
 {
     const label patchSize = this->size();
+    const word& patchName = this->patch().name();
+    const IOobject& io = this->internalField();
 
-    for (const entry& dEntry : dict_)
-    {
-        const keyType& key = dEntry.keyword();
-
-        if
-        (
-            key == "type"
-         || !dEntry.isStream() || dEntry.stream().empty()
-        )
-        {
-            continue;
-        }
-
-
-        ITstream& is = dEntry.stream();
-
-        // Read first token
-        token firstToken(is);
-
-        if (firstToken.isWord("nonuniform"))
-        {
-            token fieldToken(is);
-
-            if (!fieldToken.isCompound())
-            {
-                if
-                (
-                    fieldToken.isLabel()
-                 && fieldToken.labelToken() == 0
-                )
-                {
-                    scalarFields_.insert(key, autoPtr<scalarField>::New());
-                }
-                else
-                {
-                    FatalIOErrorInFunction(dict)
-                        << "\n    token following 'nonuniform' "
-                           "is not a compound"
-                        << "\n    on patch " << this->patch().name()
-                        << " of field "
-                        << this->internalField().name()
-                        << " in file "
-                        << this->internalField().objectPath() << nl
-                        << exit(FatalIOError);
-                }
-            }
-            else if
-            (
-                fieldToken.compoundToken().type()
-             == token::Compound<List<scalar>>::typeName
-            )
-            {
-                auto fPtr = autoPtr<scalarField>::New();
-
-                fPtr->transfer
-                (
-                    dynamicCast<token::Compound<List<scalar>>>
-                    (
-                        fieldToken.transferCompoundToken(is)
-                    )
-                );
-
-                if (fPtr->size() != patchSize)
-                {
-                    FatalIOErrorInFunction(dict)
-                        << "\n    size of field " << key
-                        << " (" << fPtr->size() << ')'
-                        << " is not the same size as the patch ("
-                        << patchSize << ')'
-                        << "\n    on patch " << this->patch().name()
-                        << " of field "
-                        << this->internalField().name()
-                        << " in file "
-                        << this->internalField().objectPath() << nl
-                        << exit(FatalIOError);
-                }
-
-                scalarFields_.insert(key, fPtr);
-            }
-            else if
-            (
-                fieldToken.compoundToken().type()
-             == token::Compound<List<vector>>::typeName
-            )
-            {
-                auto fPtr = autoPtr<vectorField>::New();
-
-                fPtr->transfer
-                (
-                    dynamicCast<token::Compound<List<vector>>>
-                    (
-                        fieldToken.transferCompoundToken(is)
-                    )
-                );
-
-                if (fPtr->size() != patchSize)
-                {
-                    FatalIOErrorInFunction(dict)
-                        << "\n    size of field " << key
-                        << " (" << fPtr->size() << ')'
-                        << " is not the same size as the patch ("
-                        << patchSize << ')'
-                        << "\n    on patch " << this->patch().name()
-                        << " of field "
-                        << this->internalField().name()
-                        << " in file "
-                        << this->internalField().objectPath() << nl
-                        << exit(FatalIOError);
-                }
-
-                vectorFields_.insert(key, fPtr);
-            }
-            else if
-            (
-                fieldToken.compoundToken().type()
-             == token::Compound<List<sphericalTensor>>::typeName
-            )
-            {
-                auto fPtr = autoPtr<sphericalTensorField>::New();
-
-                fPtr->transfer
-                (
-                    dynamicCast<token::Compound<List<sphericalTensor>>>
-                    (
-                        fieldToken.transferCompoundToken(is)
-                    )
-                );
-
-                if (fPtr->size() != patchSize)
-                {
-                    FatalIOErrorInFunction(dict)
-                        << "\n    size of field " << key
-                        << " (" << fPtr->size() << ')'
-                        << " is not the same size as the patch ("
-                        << patchSize << ')'
-                        << "\n    on patch " << this->patch().name()
-                        << " of field "
-                        << this->internalField().name()
-                        << " in file "
-                        << this->internalField().objectPath() << nl
-                        << exit(FatalIOError);
-                }
-
-                sphTensorFields_.insert(key, fPtr);
-            }
-            else if
-            (
-                fieldToken.compoundToken().type()
-             == token::Compound<List<symmTensor>>::typeName
-            )
-            {
-                auto fPtr = autoPtr<symmTensorField>::New();
-
-                fPtr->transfer
-                (
-                    dynamicCast<token::Compound<List<symmTensor>>>
-                    (
-                        fieldToken.transferCompoundToken(is)
-                    )
-                );
-
-                if (fPtr->size() != patchSize)
-                {
-                    FatalIOErrorInFunction(dict)
-                        << "\n    size of field " << key
-                        << " (" << fPtr->size() << ')'
-                        << " is not the same size as the patch ("
-                        << patchSize << ')'
-                        << "\n    on patch " << this->patch().name()
-                        << " of field "
-                        << this->internalField().name()
-                        << " in file "
-                        << this->internalField().objectPath() << nl
-                        << exit(FatalIOError);
-                }
-
-                symmTensorFields_.insert(key, fPtr);
-            }
-            else if
-            (
-                fieldToken.compoundToken().type()
-             == token::Compound<List<tensor>>::typeName
-            )
-            {
-                auto fPtr = autoPtr<tensorField>::New();
-
-                fPtr->transfer
-                (
-                    dynamicCast<token::Compound<List<tensor>>>
-                    (
-                        fieldToken.transferCompoundToken(is)
-                    )
-                );
-
-                if (fPtr->size() != patchSize)
-                {
-                    FatalIOErrorInFunction(dict)
-                        << "\n    size of field " << key
-                        << " (" << fPtr->size() << ')'
-                        << " is not the same size as the patch ("
-                        << patchSize << ')'
-                        << "\n    on patch " << this->patch().name()
-                        << " of field "
-                        << this->internalField().name()
-                        << " in file "
-                        << this->internalField().objectPath() << nl
-                        << exit(FatalIOError);
-                }
-
-                tensorFields_.insert(key, fPtr);
-            }
-            else
-            {
-                FatalIOErrorInFunction(dict)
-                    << "\n    compound " << fieldToken.compoundToken()
-                    << " not supported"
-                    << "\n    on patch " << this->patch().name()
-                    << " of field "
-                    << this->internalField().name()
-                    << " in file "
-                    << this->internalField().objectPath() << nl
-                    << exit(FatalIOError);
-            }
-        }
-    }
+    // No separate "value"
+    processGeneric(patchSize, patchName, io, false);
 }
 
 
 template<class Type>
 Foam::genericPointPatchField<Type>::genericPointPatchField
 (
-    const genericPointPatchField<Type>& ptf,
+    const genericPointPatchField<Type>& rhs,
     const pointPatch& p,
     const DimensionedField<Type, pointMesh>& iF,
     const pointPatchFieldMapper& mapper
 )
 :
-    calculatedPointPatchField<Type>(ptf, p, iF, mapper),
-    actualTypeName_(ptf.actualTypeName_),
-    dict_(ptf.dict_)
+    parent_bctype(rhs, p, iF, mapper),
+    genericPatchFieldBase(zero{}, rhs)
 {
-    forAllConstIters(ptf.scalarFields_, iter)
-    {
-        scalarFields_.insert
-        (
-            iter.key(),
-            autoPtr<scalarField>::New(*iter(), mapper)
-        );
-    }
-
-    forAllConstIters(ptf.vectorFields_, iter)
-    {
-        vectorFields_.insert
-        (
-            iter.key(),
-            autoPtr<vectorField>::New(*iter(), mapper)
-        );
-    }
-
-    forAllConstIters(ptf.sphTensorFields_, iter)
-    {
-        sphTensorFields_.insert
-        (
-            iter.key(),
-            autoPtr<sphericalTensorField>::New(*iter(), mapper)
-        );
-    }
-
-    forAllConstIters(ptf.symmTensorFields_, iter)
-    {
-        symmTensorFields_.insert
-        (
-            iter.key(),
-            autoPtr<symmTensorField>::New(*iter(), mapper)
-        );
-    }
-
-    forAllConstIters(ptf.tensorFields_, iter)
-    {
-        tensorFields_.insert
-        (
-            iter.key(),
-            autoPtr<tensorField>::New(*iter(), mapper)
-        );
-    }
+    this->mapGeneric(rhs, mapper);
 }
 
 
 template<class Type>
 Foam::genericPointPatchField<Type>::genericPointPatchField
 (
-    const genericPointPatchField<Type>& ptf,
+    const genericPointPatchField<Type>& rhs,
     const DimensionedField<Type, pointMesh>& iF
 )
 :
-    calculatedPointPatchField<Type>(ptf, iF),
-    actualTypeName_(ptf.actualTypeName_),
-    dict_(ptf.dict_),
-    scalarFields_(ptf.scalarFields_),
-    vectorFields_(ptf.vectorFields_),
-    sphTensorFields_(ptf.sphTensorFields_),
-    symmTensorFields_(ptf.symmTensorFields_),
-    tensorFields_(ptf.tensorFields_)
+    parent_bctype(rhs, iF),
+    genericPatchFieldBase(rhs)
 {}
 
 
 // * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
 
+template<class Type>
+void Foam::genericPointPatchField<Type>::write(Ostream& os) const
+{
+    // No separate treatment for "value"
+    genericPatchFieldBase::writeGeneric(os, false);
+}
+
+
 template<class Type>
 void Foam::genericPointPatchField<Type>::autoMap
 (
     const pointPatchFieldMapper& m
 )
 {
-    forAllIters(scalarFields_, iter)
-    {
-        (*iter)->autoMap(m);
-    }
-
-    forAllIters(vectorFields_, iter)
-    {
-        (*iter)->autoMap(m);
-    }
-
-    forAllIters(sphTensorFields_, iter)
-    {
-        (*iter)->autoMap(m);
-    }
-
-    forAllIters(symmTensorFields_, iter)
-    {
-        (*iter)->autoMap(m);
-    }
-
-    forAllIters(tensorFields_, iter)
-    {
-        (*iter)->autoMap(m);
-    }
+    this->autoMapGeneric(m);
 }
 
 
 template<class Type>
 void Foam::genericPointPatchField<Type>::rmap
 (
-    const pointPatchField<Type>& ptf,
+    const pointPatchField<Type>& rhs,
     const labelList& addr
 )
 {
-    const genericPointPatchField<Type>& dptf =
-        refCast<const genericPointPatchField<Type>>(ptf);
-
-    forAllIters(scalarFields_, iter)
-    {
-        const auto iter2 = dptf.scalarFields_.cfind(iter.key());
-
-        if (iter2.found())
-        {
-            (*iter)->rmap(*iter2(), addr);
-        }
-    }
-
-    forAllIters(vectorFields_, iter)
+    const auto* base = isA<genericPatchFieldBase>(rhs);
+    if (base)
     {
-        const auto iter2 = dptf.vectorFields_.cfind(iter.key());
-
-        if (iter2.found())
-        {
-            (*iter)->rmap(*iter2(), addr);
-        }
-    }
-
-    forAllIters(sphTensorFields_, iter)
-    {
-        const auto iter2 = dptf.sphTensorFields_.find(iter.key());
-
-        if (iter2.found())
-        {
-            (*iter)->rmap(*iter2(), addr);
-        }
-    }
-
-    forAllIters(symmTensorFields_, iter)
-    {
-        const auto iter2 = dptf.symmTensorFields_.find(iter.key());
-
-        if (iter2.found())
-        {
-            (*iter)->rmap(*iter2(), addr);
-        }
-    }
-
-    forAllIters(tensorFields_, iter)
-    {
-        const auto iter2 = dptf.tensorFields_.find(iter.key());
-
-        if (iter2.found())
-        {
-            (*iter)->rmap(*iter2(), addr);
-        }
-    }
-}
-
-
-template<class Type>
-const Foam::word& Foam::genericPointPatchField<Type>::actualType() const
-{
-    return actualTypeName_;
-}
-
-
-template<class Type>
-void Foam::genericPointPatchField<Type>::write(Ostream& os) const
-{
-    os.writeEntry("type", actualTypeName_);
-
-    for (const entry& dEntry : dict_)
-    {
-        const keyType& key = dEntry.keyword();
-
-        if (key == "type")
-        {
-            // NB: "type" written first, no special treatment for "value"
-            continue;
-        }
-        else if
-        (
-            dEntry.isStream()
-         && dEntry.stream().size()
-         && dEntry.stream()[0].isWord("nonuniform")
-        )
-        {
-            if (scalarFields_.found(key))
-            {
-                scalarFields_.cfind(key)()->writeEntry(key, os);
-            }
-            else if (vectorFields_.found(key))
-            {
-                vectorFields_.cfind(key)()->writeEntry(key, os);
-            }
-            else if (sphTensorFields_.found(key))
-            {
-                sphTensorFields_.cfind(key)()->writeEntry(key, os);
-            }
-            else if (symmTensorFields_.found(key))
-            {
-                symmTensorFields_.cfind(key)()->writeEntry(key, os);
-            }
-            else if (tensorFields_.found(key))
-            {
-                tensorFields_.cfind(key)()->writeEntry(key, os);
-            }
-        }
-        else
-        {
-            dEntry.write(os);
-        }
+        this->rmapGeneric(*base, addr);
     }
 }
 
diff --git a/src/genericPatchFields/genericPointPatchField/genericPointPatchField.H b/src/genericPatchFields/genericPointPatchField/genericPointPatchField.H
index 065a1751577dbca347b6028104f144fc358ca3f2..59b3be11f4b11bf0fbb24dd091a06a877ecff223 100644
--- a/src/genericPatchFields/genericPointPatchField/genericPointPatchField.H
+++ b/src/genericPatchFields/genericPointPatchField/genericPointPatchField.H
@@ -6,7 +6,7 @@
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
     Copyright (C) 2011-2016 OpenFOAM Foundation
-    Copyright (C) 2016-2019 OpenCFD Ltd.
+    Copyright (C) 2016-2021 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -40,7 +40,7 @@ SourceFiles
 #define genericPointPatchField_H
 
 #include "calculatedPointPatchField.H"
-#include "HashPtrTable.H"
+#include "genericPatchFieldBase.H"
 
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
 
@@ -54,19 +54,11 @@ namespace Foam
 template<class Type>
 class genericPointPatchField
 :
-    public calculatedPointPatchField<Type>
+    public calculatedPointPatchField<Type>,
+    public genericPatchFieldBase
 {
-    // Private Data
-
-        const word actualTypeName_;
-
-        dictionary dict_;
-
-        HashPtrTable<scalarField> scalarFields_;
-        HashPtrTable<vectorField> vectorFields_;
-        HashPtrTable<sphericalTensorField> sphTensorFields_;
-        HashPtrTable<symmTensorField> symmTensorFields_;
-        HashPtrTable<tensorField> tensorFields_;
+    //- The parent boundary condition type
+    typedef calculatedPointPatchField<Type> parent_bctype;
 
 
 public:
@@ -113,6 +105,9 @@ public:
             );
         }
 
+        //- Default copy construct
+        genericPointPatchField(const genericPointPatchField<Type>&) = default;
+
         //- Construct as copy setting internal field reference
         genericPointPatchField
         (
@@ -139,27 +134,21 @@ public:
 
     // Member Functions
 
-        // Mapping Functions
-
-            //- Map (and resize as needed) from self given a mapping object
-            virtual void autoMap
-            (
-                const pointPatchFieldMapper&
-            );
+        //- Write
+        virtual void write(Ostream&) const;
 
-            //- Reverse map the given pointPatchField onto this pointPatchField
-            virtual void rmap
-            (
-                const pointPatchField<Type>&,
-                const labelList&
-            );
 
+    // Mapping Functions
 
-        //- Return the actual type
-        const word& actualType() const;
+        //- Map (and resize as needed) from self given a mapping object
+        virtual void autoMap(const pointPatchFieldMapper&);
 
-        //- Write
-        virtual void write(Ostream&) const;
+        //- Reverse map the given pointPatchField onto this pointPatchField
+        virtual void rmap
+        (
+            const pointPatchField<Type>&,
+            const labelList&
+        );
 };