Add serialization-only user defined type macros (#3816)

diff --git a/docs/examples/nlohmann_define_type_intrusive_only_serialize_explicit.cpp b/docs/examples/nlohmann_define_type_intrusive_only_serialize_explicit.cpp
new file mode 100644
index 0000000..ea422be
--- /dev/null
+++ b/docs/examples/nlohmann_define_type_intrusive_only_serialize_explicit.cpp
@@ -0,0 +1,38 @@
+#include <iostream>
+#include <nlohmann/json.hpp>
+
+using json = nlohmann::json;
+using namespace nlohmann::literals;
+
+namespace ns
+{
+class person
+{
+  private:
+    std::string name = "John Doe";
+    std::string address = "123 Fake St";
+    int age = -1;
+
+  public:
+    // No default constructor
+    person(std::string name_, std::string address_, int age_)
+        : name(std::move(name_)), address(std::move(address_)), age(age_)
+    {}
+
+    friend void to_json(nlohmann::json& nlohmann_json_j, const person& nlohmann_json_t)
+    {
+        nlohmann_json_j["name"] = nlohmann_json_t.name;
+        nlohmann_json_j["address"] = nlohmann_json_t.address;
+        nlohmann_json_j["age"] = nlohmann_json_t.age;
+    }
+};
+} // namespace ns
+
+int main()
+{
+    ns::person p = {"Ned Flanders", "744 Evergreen Terrace", 60};
+
+    // serialization: person -> json
+    json j = p;
+    std::cout << "serialization: " << j << std::endl;
+}
diff --git a/docs/examples/nlohmann_define_type_intrusive_only_serialize_explicit.output b/docs/examples/nlohmann_define_type_intrusive_only_serialize_explicit.output
new file mode 100644
index 0000000..12f5033
--- /dev/null
+++ b/docs/examples/nlohmann_define_type_intrusive_only_serialize_explicit.output
@@ -0,0 +1 @@
+serialization: {"address":"744 Evergreen Terrace","age":60,"name":"Ned Flanders"}
diff --git a/docs/examples/nlohmann_define_type_intrusive_only_serialize_macro.cpp b/docs/examples/nlohmann_define_type_intrusive_only_serialize_macro.cpp
new file mode 100644
index 0000000..8213f90
--- /dev/null
+++ b/docs/examples/nlohmann_define_type_intrusive_only_serialize_macro.cpp
@@ -0,0 +1,33 @@
+#include <iostream>
+#include <nlohmann/json.hpp>
+
+using json = nlohmann::json;
+using namespace nlohmann::literals;
+
+namespace ns
+{
+class person
+{
+  private:
+    std::string name = "John Doe";
+    std::string address = "123 Fake St";
+    int age = -1;
+
+  public:
+    // No default constructor
+    person(std::string name_, std::string address_, int age_)
+        : name(std::move(name_)), address(std::move(address_)), age(age_)
+    {}
+
+    NLOHMANN_DEFINE_TYPE_INTRUSIVE(person, name, address, age)
+};
+} // namespace ns
+
+int main()
+{
+    ns::person p = {"Ned Flanders", "744 Evergreen Terrace", 60};
+
+    // serialization: person -> json
+    json j = p;
+    std::cout << "serialization: " << j << std::endl;
+}
diff --git a/docs/examples/nlohmann_define_type_intrusive_only_serialize_macro.output b/docs/examples/nlohmann_define_type_intrusive_only_serialize_macro.output
new file mode 100644
index 0000000..12f5033
--- /dev/null
+++ b/docs/examples/nlohmann_define_type_intrusive_only_serialize_macro.output
@@ -0,0 +1 @@
+serialization: {"address":"744 Evergreen Terrace","age":60,"name":"Ned Flanders"}
diff --git a/docs/examples/nlohmann_define_type_non_intrusive_only_serialize_explicit.cpp b/docs/examples/nlohmann_define_type_non_intrusive_only_serialize_explicit.cpp
new file mode 100644
index 0000000..8890e03
--- /dev/null
+++ b/docs/examples/nlohmann_define_type_non_intrusive_only_serialize_explicit.cpp
@@ -0,0 +1,31 @@
+#include <iostream>
+#include <nlohmann/json.hpp>
+
+using json = nlohmann::json;
+using namespace nlohmann::literals;
+
+namespace ns
+{
+struct person
+{
+    std::string name;
+    std::string address;
+    int age;
+};
+
+void to_json(nlohmann::json& nlohmann_json_j, const person& nlohmann_json_t)
+{
+    nlohmann_json_j["name"] = nlohmann_json_t.name;
+    nlohmann_json_j["address"] = nlohmann_json_t.address;
+    nlohmann_json_j["age"] = nlohmann_json_t.age;
+}
+} // namespace ns
+
+int main()
+{
+    ns::person p = {"Ned Flanders", "744 Evergreen Terrace", 60};
+
+    // serialization: person -> json
+    json j = p;
+    std::cout << "serialization: " << j << std::endl;
+}
diff --git a/docs/examples/nlohmann_define_type_non_intrusive_only_serialize_explicit.output b/docs/examples/nlohmann_define_type_non_intrusive_only_serialize_explicit.output
new file mode 100644
index 0000000..12f5033
--- /dev/null
+++ b/docs/examples/nlohmann_define_type_non_intrusive_only_serialize_explicit.output
@@ -0,0 +1 @@
+serialization: {"address":"744 Evergreen Terrace","age":60,"name":"Ned Flanders"}
diff --git a/docs/examples/nlohmann_define_type_non_intrusive_only_serialize_macro.cpp b/docs/examples/nlohmann_define_type_non_intrusive_only_serialize_macro.cpp
new file mode 100644
index 0000000..7aef23a
--- /dev/null
+++ b/docs/examples/nlohmann_define_type_non_intrusive_only_serialize_macro.cpp
@@ -0,0 +1,26 @@
+#include <iostream>
+#include <nlohmann/json.hpp>
+
+using json = nlohmann::json;
+using namespace nlohmann::literals;
+
+namespace ns
+{
+struct person
+{
+    std::string name;
+    std::string address;
+    int age;
+};
+
+NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_ONLY_SERIALIZE(person, name, address, age)
+} // namespace ns
+
+int main()
+{
+    ns::person p = {"Ned Flanders", "744 Evergreen Terrace", 60};
+
+    // serialization: person -> json
+    json j = p;
+    std::cout << "serialization: " << j << std::endl;
+}
diff --git a/docs/examples/nlohmann_define_type_non_intrusive_only_serialize_macro.output b/docs/examples/nlohmann_define_type_non_intrusive_only_serialize_macro.output
new file mode 100644
index 0000000..12f5033
--- /dev/null
+++ b/docs/examples/nlohmann_define_type_non_intrusive_only_serialize_macro.output
@@ -0,0 +1 @@
+serialization: {"address":"744 Evergreen Terrace","age":60,"name":"Ned Flanders"}
diff --git a/docs/mkdocs/docs/api/macros/index.md b/docs/mkdocs/docs/api/macros/index.md
index 099dfa6..ae9eb20 100644
--- a/docs/mkdocs/docs/api/macros/index.md
+++ b/docs/mkdocs/docs/api/macros/index.md
@@ -50,9 +50,11 @@
 
 ## Serialization/deserialization macros
 
-- [**NLOHMANN_DEFINE_TYPE_INTRUSIVE(type, member...)**<br>**NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(type, member...)**][DefInt]
+- [**NLOHMANN_DEFINE_TYPE_INTRUSIVE(type, member...)**<br>**NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(type, member...)**
+<br>**NLOHMANN_DEFINE_TYPE_INTRUSIVE_ONLY_SERIALIZE(type, member...)**][DefInt]
   \- serialization/deserialization of types _with_ access to private variables
-- [**NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(type, member...)**<br>**NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(type, member...)**][DefNonInt]
+- [**NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(type, member...)**<br>**NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(type, member...)**
+<br>**NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_ONLY_SERIALIZE(type, member...)**][DefNonInt]
   \- serialization/deserialization of types _without_ access to private variables
 - [**NLOHMANN_JSON_SERIALIZE_ENUM(type, ...)**](nlohmann_json_serialize_enum.md) - serialization/deserialization of enum types
 
diff --git a/docs/mkdocs/docs/api/macros/nlohmann_define_type_intrusive.md b/docs/mkdocs/docs/api/macros/nlohmann_define_type_intrusive.md
index 88df1d1..ad42581 100644
--- a/docs/mkdocs/docs/api/macros/nlohmann_define_type_intrusive.md
+++ b/docs/mkdocs/docs/api/macros/nlohmann_define_type_intrusive.md
@@ -1,8 +1,9 @@
-# NLOHMANN_DEFINE_TYPE_INTRUSIVE, NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT
+# NLOHMANN_DEFINE_TYPE_INTRUSIVE, NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT, NLOHMANN_DEFINE_TYPE_INTRUSIVE_ONLY_SERIALIZE
 
 ```cpp
 #define NLOHMANN_DEFINE_TYPE_INTRUSIVE(type, member...)              // (1)
 #define NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(type, member...) // (2)
+#define NLOHMANN_DEFINE_TYPE_INTRUSIVE_ONLY_SERIALIZE(type, member...) // (3)
 ```
 
 These macros can be used to simplify the serialization/deserialization of types if you want to use a JSON object as
@@ -16,6 +17,7 @@
 2. Will use [`value`](../basic_json/value.md) during deserialization and fall back to the default value for the
    respective type of the member variable if a key in the JSON object is missing. The generated `from_json()` function
    default constructs an object and uses its values as the defaults when calling the `value` function.
+3. Only defines the serialization. Useful in cases when the type does not have a default constructor and only serialization in required.
 
 ## Parameters
 
@@ -31,7 +33,7 @@
 
 ```cpp
 friend void to_json(nlohmann::json&, const type&);
-friend void from_json(const nlohmann::json&, type&);
+friend void from_json(const nlohmann::json&, type&); // except (3)
 ```
 
 See examples below for the concrete generated code.
@@ -40,7 +42,7 @@
 
 !!! info "Prerequisites"
 
-    1. The type `type` must be default constructible. See [How can I use `get()` for non-default
+    1. The type `type` must be default constructible (except (3)). See [How can I use `get()` for non-default
        constructible/non-copyable types?][GetNonDefNonCopy] for how to overcome this limitation.
     2. The macro must be used inside the type (class/struct).
 
@@ -108,15 +110,42 @@
 
     The macro is equivalent to:
 
-    ```cpp hl_lines="21 22 23 24 25 26 27 28 29 30 31 32 33 34 35"
+    ```cpp hl_lines="22 23 24 25 26 27 28 29 30 31 32 33 34 35"
     --8<-- "examples/nlohmann_define_type_intrusive_with_default_explicit.cpp"
     ```
 
     Note how a default-initialized `person` object is used in the `from_json` to fill missing values.
 
+??? example "Example (3): NLOHMANN_DEFINE_TYPE_INTRUSIVE_ONLY_SERIALIZE"
+    Consider the following complete example:
+
+    ```cpp hl_lines="22"
+    --8<-- "examples/nlohmann_define_type_intrusive_only_serialize_macro.cpp"
+    ```
+    
+    Output:
+    
+    ```json
+    --8<-- "examples/nlohmann_define_type_intrusive_only_serialize_macro.output"
+    ```
+
+    Notes:
+
+    - `ns::person` is non-default-constructible. This allows this macro to be used instead of 
+      `NLOHMANN_DEFINE_TYPE_INTRUSIVE` and `NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT`.
+    - `ns::person` has private member variables. This makes `NLOHMANN_DEFINE_TYPE_INTRUSIVE_ONLY_SERIALIZE` applicable, but not
+      `NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_ONLY_SERIALIZE`.
+    - The macro `NLOHMANN_DEFINE_TYPE_INTRUSIVE_ONLY_SERIALIZE` is used _inside_ the class.
+
+    The macro is equivalent to:
+
+    ```cpp hl_lines="22 22 23 24 25 26 27"
+    --8<-- "examples/nlohmann_define_type_intrusive_only_serialize_explicit.cpp"
+    ```
+
 ## See also
 
-- [NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE / NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT](nlohmann_define_type_non_intrusive.md)
+- [NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE{_WITH_DEFAULT, _ONLY_SERIALIZE}](nlohmann_define_type_non_intrusive.md)
   for a similar macro that can be defined _outside_ the type.
 - [Arbitrary Type Conversions](../../features/arbitrary_types.md) for an overview.
 
@@ -124,3 +153,4 @@
 
 1. Added in version 3.9.0.
 2. Added in version 3.11.0.
+3. Added in version TODO.
diff --git a/docs/mkdocs/docs/api/macros/nlohmann_define_type_non_intrusive.md b/docs/mkdocs/docs/api/macros/nlohmann_define_type_non_intrusive.md
index e1f1991..5830f8c 100644
--- a/docs/mkdocs/docs/api/macros/nlohmann_define_type_non_intrusive.md
+++ b/docs/mkdocs/docs/api/macros/nlohmann_define_type_non_intrusive.md
@@ -1,8 +1,9 @@
-# NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE, NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT
+# NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE, NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT, NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_ONLY_SERIALIZE
 
 ```cpp
 #define NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(type, member...)              // (1)
 #define NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(type, member...) // (2)
+#define NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_ONLY_SERIALIZE(type, member...) // (3)
 ```
 
 These macros can be used to simplify the serialization/deserialization of types if you want to use a JSON object as
@@ -16,6 +17,7 @@
 2. Will use [`value`](../basic_json/value.md) during deserialization and fall back to the default value for the
    respective type of the member variable if a key in the JSON object is missing. The generated `from_json()` function
    default constructs an object and uses its values as the defaults when calling the `value` function.
+3. Only defines the serialization. Useful in cases when the type does not have a default constructor and only serialization in required.
 
 ## Parameters
 
@@ -31,7 +33,7 @@
 
 ```cpp
 void to_json(nlohmann::json&, const type&);
-void from_json(const nlohmann::json&, type&);
+void from_json(const nlohmann::json&, type&); // except (3)
 ```
 
 See examples below for the concrete generated code.
@@ -40,7 +42,7 @@
 
 !!! info "Prerequisites"
 
-    1. The type `type` must be default constructible. See [How can I use `get()` for non-default constructible/non-copyable types?][GetNonDefNonCopy]
+    1. The type `type` must be default constructible (except (3). See [How can I use `get()` for non-default constructible/non-copyable types?][GetNonDefNonCopy]
        for how to overcome this limitation.
     2. The macro must be used outside the type (class/struct).
     3. The passed members must be public.
@@ -88,7 +90,7 @@
 
     Consider the following complete example:
 
-    ```cpp hl_lines="21"
+    ```cpp hl_lines="22"
     --8<-- "examples/nlohmann_define_type_non_intrusive_with_default_macro.cpp"
     ```
     
@@ -109,15 +111,42 @@
 
     The macro is equivalent to:
 
-    ```cpp hl_lines="21 22 23 24 25 26 27 28 29 30 31 32 33 34"
+    ```cpp hl_lines="22 23 24 25 26 27 28 29 30 31 32 33 34 35"
     --8<-- "examples/nlohmann_define_type_non_intrusive_with_default_explicit.cpp"
     ```
 
     Note how a default-initialized `person` object is used in the `from_json` to fill missing values.
 
+??? example "Example (3): NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_ONLY_SERIALIZE"
+
+    Consider the following complete example:
+
+    ```cpp hl_lines="16"
+    --8<-- "examples/nlohmann_define_type_non_intrusive_only_serialize_macro.cpp"
+    ```
+    
+    Output:
+    
+    ```json
+    --8<-- "examples/nlohmann_define_type_non_intrusive_only_serialize_macro.output"
+    ```
+
+    Notes:
+
+    - `ns::person` is non-default-constructible. This allows this macro to be used instead of 
+      `NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE` and `NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT`.
+    - `ns::person` has only public member variables. This makes `NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_ONLY_SERIALIZE` applicable.
+    - The macro `NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_ONLY_SERIALIZE` is used _outside_ the class, but _inside_ its namespace `ns`.
+
+    The macro is equivalent to:
+
+    ```cpp hl_lines="16 17 18 19 20 21"
+    --8<-- "examples/nlohmann_define_type_non_intrusive_only_serialize_explicit.cpp"
+    ```
+
 ## See also
 
-- [NLOHMANN_DEFINE_TYPE_INTRUSIVE / NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT](nlohmann_define_type_intrusive.md)
+- [NLOHMANN_DEFINE_TYPE_INTRUSIVE{_WITH_DEFAULT, _ONLY_SERIALIZE}](nlohmann_define_type_intrusive.md)
   for a similar macro that can be defined _inside_ the type.
 - [Arbitrary Type Conversions](../../features/arbitrary_types.md) for an overview.
 
@@ -125,3 +154,4 @@
 
 1. Added in version 3.9.0.
 2. Added in version 3.11.0.
+3. Added in version TODO.
diff --git a/docs/mkdocs/docs/features/macros.md b/docs/mkdocs/docs/features/macros.md
index 1be95d3..926741b 100644
--- a/docs/mkdocs/docs/features/macros.md
+++ b/docs/mkdocs/docs/features/macros.md
@@ -119,6 +119,13 @@
 
 See [full documentation of `NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT`](../api/macros/nlohmann_define_type_intrusive.md).
 
+## `NLOHMANN_DEFINE_TYPE_INTRUSIVE_ONLY_SERIALIZE(type, member...)`
+
+This macro is similar to `NLOHMANN_DEFINE_TYPE_INTRUSIVE` except that it defines only the serialization code. This is
+useful when the user type does not have a default constructor and only the serialization is required.
+
+See [full documentation of `NLOHMANN_DEFINE_TYPE_INTRUSIVE_ONLY_SERIALIZE`](../api/macros//nlohmann_define_type_intrusive.md).
+
 ## `NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(type, member...)`
 
 This macro can be used to simplify the serialization/deserialization of types if (1) want to use a JSON object as
@@ -138,6 +145,13 @@
 
 See [full documentation of `NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT`](../api/macros/nlohmann_define_type_non_intrusive.md).
 
+## `NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_ONLY_SERIALIZE(type, member...)`
+
+This macro is similar to `NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE` except that it defines only the serialization code. This is
+useful when the user type does not have a default constructor and only the serialization is required.
+
+See [full documentation of `NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_ONLY_SERIALIZE`](../api/macros//nlohmann_define_type_non_intrusive.md).
+
 ## `NLOHMANN_JSON_SERIALIZE_ENUM(type, ...)`
 
 This macro simplifies the serialization/deserialization of enum types. See
diff --git a/include/nlohmann/detail/macro_scope.hpp b/include/nlohmann/detail/macro_scope.hpp
index faa24b5..5d395c7 100644
--- a/include/nlohmann/detail/macro_scope.hpp
+++ b/include/nlohmann/detail/macro_scope.hpp
@@ -406,6 +406,9 @@
     friend void to_json(nlohmann::json& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \
     friend void from_json(const nlohmann::json& nlohmann_json_j, Type& nlohmann_json_t) { const Type nlohmann_json_default_obj{}; NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM_WITH_DEFAULT, __VA_ARGS__)) }
 
+#define NLOHMANN_DEFINE_TYPE_INTRUSIVE_ONLY_SERIALIZE(Type, ...)  \
+    friend void to_json(nlohmann::json& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) }
+
 /*!
 @brief macro
 @def NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE
@@ -415,6 +418,9 @@
     inline void to_json(nlohmann::json& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \
     inline void from_json(const nlohmann::json& nlohmann_json_j, Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM, __VA_ARGS__)) }
 
+#define NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_ONLY_SERIALIZE(Type, ...)  \
+    inline void to_json(nlohmann::json& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) }
+
 #define NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(Type, ...)  \
     inline void to_json(nlohmann::json& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \
     inline void from_json(const nlohmann::json& nlohmann_json_j, Type& nlohmann_json_t) { const Type nlohmann_json_default_obj{}; NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM_WITH_DEFAULT, __VA_ARGS__)) }
diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp
index dc58ca1..f453857 100644
--- a/single_include/nlohmann/json.hpp
+++ b/single_include/nlohmann/json.hpp
@@ -2758,6 +2758,9 @@
     friend void to_json(nlohmann::json& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \
     friend void from_json(const nlohmann::json& nlohmann_json_j, Type& nlohmann_json_t) { const Type nlohmann_json_default_obj{}; NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM_WITH_DEFAULT, __VA_ARGS__)) }
 
+#define NLOHMANN_DEFINE_TYPE_INTRUSIVE_ONLY_SERIALIZE(Type, ...)  \
+    friend void to_json(nlohmann::json& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) }
+
 /*!
 @brief macro
 @def NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE
@@ -2767,6 +2770,9 @@
     inline void to_json(nlohmann::json& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \
     inline void from_json(const nlohmann::json& nlohmann_json_j, Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM, __VA_ARGS__)) }
 
+#define NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_ONLY_SERIALIZE(Type, ...)  \
+    inline void to_json(nlohmann::json& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) }
+
 #define NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(Type, ...)  \
     inline void to_json(nlohmann::json& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \
     inline void from_json(const nlohmann::json& nlohmann_json_j, Type& nlohmann_json_t) { const Type nlohmann_json_default_obj{}; NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM_WITH_DEFAULT, __VA_ARGS__)) }
diff --git a/tests/src/unit-udt_macro.cpp b/tests/src/unit-udt_macro.cpp
index ac9d9d2..c54ecac 100644
--- a/tests/src/unit-udt_macro.cpp
+++ b/tests/src/unit-udt_macro.cpp
@@ -279,6 +279,44 @@
 
 NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(person_with_public_alphabet, a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z)
 
+class person_without_default_constructor_1
+{
+  public:
+    std::string name;
+    int age;
+
+    bool operator==(const person_without_default_constructor_1& other) const
+    {
+        return name == other.name && age == other.age;
+    }
+
+    person_without_default_constructor_1(std::string name_, int age_)
+        : name{std::move(name_)}
+        , age{age_}
+    {}
+
+    NLOHMANN_DEFINE_TYPE_INTRUSIVE_ONLY_SERIALIZE(person_without_default_constructor_1, name, age)
+};
+
+class person_without_default_constructor_2
+{
+  public:
+    std::string name;
+    int age;
+
+    bool operator==(const person_without_default_constructor_2& other) const
+    {
+        return name == other.name && age == other.age;
+    }
+
+    person_without_default_constructor_2(std::string name_, int age_)
+        : name{std::move(name_)}
+        , age{age_}
+    {}
+};
+
+NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_ONLY_SERIALIZE(person_without_default_constructor_2, name, age)
+
 } // namespace persons
 
 TEST_CASE_TEMPLATE("Serialization/deserialization via NLOHMANN_DEFINE_TYPE_INTRUSIVE and NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE", T,
@@ -412,3 +450,25 @@
         }
     }
 }
+
+TEST_CASE_TEMPLATE("Serialization of non-default-constructible classes via NLOHMANN_DEFINE_TYPE_INTRUSIVE_ONLY_SERIALIZE and NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_ONLY_SERIALIZE", T,
+                   persons::person_without_default_constructor_1,
+                   persons::person_without_default_constructor_2)
+{
+    SECTION("person")
+    {
+        {
+            // serialization of a single object
+            T person{"Erik", 1};
+            CHECK(json(person).dump() == "{\"age\":1,\"name\":\"Erik\"}");
+
+            // serialization of a container with objects
+            std::vector<T> const two_persons
+            {
+                {"Erik", 1},
+                {"Kyle", 2}
+            };
+            CHECK(json(two_persons).dump() == "[{\"age\":1,\"name\":\"Erik\"},{\"age\":2,\"name\":\"Kyle\"}]");
+        }
+    }
+}