File: README.rst

package info (click to toggle)
dataclass-wizard 0.35.1-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 1,808 kB
  • sloc: python: 15,276; makefile: 111; javascript: 23
file content (1622 lines) | stat: -rw-r--r-- 56,389 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
================
Dataclass Wizard
================

Release v\ |version| | 📚 Full docs on `Read the Docs`_ (`Installation`_).

**Dataclass Wizard** 🪄
Simple, elegant *wizarding* tools for Python’s ``dataclasses``.

Lightning-fast ⚡, pure Python, and lightweight — effortlessly
convert dataclass instances to/from JSON, perfect
for complex and *nested dataclass* models!

-------------------

**Behold, the power of the Dataclass Wizard**::

    >>> from __future__ import annotations
    >>> from dataclasses import dataclass, field
    >>> from dataclass_wizard import JSONWizard
    ...
    >>> @dataclass
    ... class MyClass(JSONWizard):
    ...     my_str: str | None
    ...     is_active_tuple: tuple[bool, ...]
    ...     list_of_int: list[int] = field(default_factory=list)
    ...
    >>> string = """
    ... {
    ...   "my_str": 20,
    ...   "ListOfInt": ["1", "2", 3],
    ...   "isActiveTuple": ["true", false, 1]
    ... }
    ... """
    ...
    >>> instance = MyClass.from_json(string)
    >>> instance
    MyClass(my_str='20', is_active_tuple=(True, False, True), list_of_int=[1, 2, 3])
    >>> instance.to_json()
    '{"myStr": "20", "isActiveTuple": [true, false, true], "listOfInt": [1, 2, 3]}'
    >>> instance == MyClass.from_dict(instance.to_dict())
    True

---

.. contents:: Contents
   :depth: 1
   :local:
   :backlinks: none

``v1`` Opt-In 🚀
----------------

Early access to **V1** is available! To opt in, simply enable ``v1=True`` in the ``Meta`` settings:

.. code-block:: python3

    from dataclasses import dataclass
    from dataclass_wizard import JSONPyWizard
    from dataclass_wizard.v1 import Alias

    @dataclass
    class A(JSONPyWizard):
        class _(JSONPyWizard.Meta):
            v1 = True

        my_str: str
        version_info: float = Alias(load='v-info')

    # Alternatively, for simple dataclasses that don't subclass `JSONPyWizard`:
    # LoadMeta(v1=True).bind_to(A)

    a = A.from_dict({'my_str': 'test', 'v-info': '1.0'})
    assert a.version_info == 1.0
    assert a.to_dict() == {'my_str': 'test', 'version_info': 1.0}

For more information, see the `Field Guide to V1 Opt-in`_.

Performance Improvements
~~~~~~~~~~~~~~~~~~~~~~~~

The upcoming **V1** release brings significant performance improvements in de/serialization. Personal benchmarks show that **V1** can make Dataclass Wizard
approximately **2x faster** than ``pydantic``!

While some features are still being refined and fully supported, **v1** positions Dataclass Wizard alongside other high-performance serialization libraries in Python.

Why Use Dataclass Wizard?
-------------------------

Effortlessly handle complex data with one of the *fastest* and *lightweight* libraries available! Perfect for APIs, JSON wrangling, and more.

- 🚀 **Blazing Fast** — One of the fastest libraries out there!
- 🪶 **Lightweight** — Pure Python, minimal dependencies
- 👶 Easy Setup — Intuitive, hassle-free
- ☝️ **Battle-Tested** — Proven reliability with solid test coverage
- ⚙️ Highly Customizable — Endless de/serialization options to fit your needs
- 🎉 Built-in Support — JSON, YAML, TOML, and environment/settings management
- 📦 **Full Python Type Support** — Powered by type hints with full support for native types and ``typing-extensions``
- 📝 Auto-Generate Schemas — JSON to Dataclass made easy

Key Features
------------

- 🔄 Flexible (de)serialization — Marshal dataclasses to/from JSON, TOML, YAML, or ``dict`` with ease.
- 🌿 Environment Magic — Map env vars and ``.env`` files to strongly-typed class fields effortlessly.
- 🧑‍💻 Field Properties Made Simple — Add properties with default values to your dataclasses.
- 🧙‍♂️ JSON-to-Dataclass Wizardry — Auto-generate a dataclass schema from any JSON file or string instantly.

Installation
------------

*Dataclass Wizard* is available on `PyPI`_. You can install it with ``pip``:

.. code-block:: console

    $ pip install dataclass-wizard

Also available on `conda`_ via `conda-forge`_. To install via ``conda``:

.. code-block:: console

    $ conda install dataclass-wizard -c conda-forge

This library supports **Python 3.9+**. Support for Python 3.6 – 3.8 was
available in earlier releases but is no longer maintained, as those
versions no longer receive security updates.

For convenience, the table below outlines the last compatible release
of *Dataclass Wizard* for unsupported Python versions (3.6 – 3.8):

.. list-table::
   :header-rows: 1
   :widths: 15 35 15

   * - Python Version
     - Last Version of ``dataclass-wizard``
     - Python EOL
   * - 3.8
     - 0.26.1_
     - 2024-10-07
   * - 3.7
     - 0.26.1_
     - 2023-06-27
   * - 3.6
     - 0.26.1_
     - 2021-12-23

.. _0.26.1: https://pypi.org/project/dataclass-wizard/0.26.1/
.. _PyPI: https://pypi.org/project/dataclass-wizard/
.. _conda: https://anaconda.org/conda-forge/dataclass-wizard
.. _conda-forge: https://conda-forge.org/
.. _Changelog: https://dataclass-wizard.readthedocs.io/en/latest/history.html

See the package on `PyPI`_ and the `Changelog`_ in the docs for the latest version details.

Wizard Mixins ✨
----------------

In addition to ``JSONWizard``, these `Mixin`_ classes simplify common tasks and make your data handling *spellbindingly* efficient:

- 🪄 `EnvWizard`_ — Load environment variables and `.env` files into typed schemas, even supporting secret files (keys as file names).
- 🎩 `JSONPyWizard`_ — A helper for ``JSONWizard`` that preserves your keys as-is (no camelCase changes).
- 🔮 `JSONListWizard`_ — Extend ``JSONWizard`` to convert lists into `Container`_ objects.
- 💼 `JSONFileWizard`_ — Convert dataclass instances to/from local JSON files with ease.
- 🌳 `TOMLWizard`_ — Map your dataclasses to/from TOML format.
- 🧙‍♂️ `YAMLWizard`_ — Convert between YAML and dataclass instances using ``PyYAML``.

Supported Types 🧑‍💻
---------------------

*Dataclass Wizard* supports:

- 📋 **Collections**: Handle ``list``, ``dict``, and ``set`` effortlessly.
- 🔢 **Typing Generics**: Manage ``Union``, ``Any``, and other types from the `typing`_ module.
- 🌟 **Advanced Types**: Work with ``Enum``, ``defaultdict``, and ``datetime`` with ease.

For more info, check out the `Supported Types`_ section in the docs for detailed insights into each type and the load/dump process!

Usage and Examples
------------------

.. rubric:: Seamless JSON De/Serialization with ``JSONWizard``

.. code-block:: python3

    from __future__ import annotations  # Optional in Python 3.10+

    from dataclasses import dataclass, field
    from enum import Enum
    from datetime import date

    from dataclass_wizard import JSONWizard


    @dataclass
    class Data(JSONWizard):
        # Use Meta to customize JSON de/serialization
        class _(JSONWizard.Meta):
            key_transform_with_dump = 'LISP'  # Transform keys to LISP-case during dump

        a_sample_bool: bool
        values: list[Inner] = field(default_factory=list)


    @dataclass
    class Inner:
        # Nested data with optional enums and typed dictionaries
        vehicle: Car | None
        my_dates: dict[int, date]


    class Car(Enum):
        SEDAN = 'BMW Coupe'
        SUV = 'Toyota 4Runner'


    # Input JSON-like dictionary
    my_dict = {
        'values': [{'vehicle': 'Toyota 4Runner', 'My-Dates': {'123': '2023-01-31'}}],
        'aSampleBool': 'TRUE'
    }

    # Deserialize into strongly-typed dataclass instances
    data = Data.from_dict(my_dict)
    print((v := data.values[0]).vehicle)  # Prints: <Car.SUV: 'Toyota 4Runner'>
    assert v.my_dates[123] == date(2023, 1, 31)  # > True

    # Serialize back into pretty-printed JSON
    print(data.to_json(indent=2))

.. rubric:: Map Environment Variables with ``EnvWizard``

Easily map environment variables to Python dataclasses:

.. code-block:: python3

    import os
    from dataclass_wizard import EnvWizard

    os.environ.update({
        'APP_NAME': 'My App',
        'MAX_CONNECTIONS': '10',
        'DEBUG_MODE': 'true'
    })

    class AppConfig(EnvWizard):
        app_name: str
        max_connections: int
        debug_mode: bool

    config = AppConfig()
    print(config.app_name)    # My App
    print(config.debug_mode)  # True

📖 See more `on EnvWizard`_ in the full documentation.

.. rubric:: Dataclass Properties with ``property_wizard``

Add field properties to your dataclasses with default values using ``property_wizard``:

.. code-block:: python3

    from __future__ import annotations  # This can be removed in Python 3.10+

    from dataclasses import dataclass, field
    from typing_extensions import Annotated

    from dataclass_wizard import property_wizard


    @dataclass
    class Vehicle(metaclass=property_wizard):
        wheels: Annotated[int | str, field(default=4)]
        # or, alternatively:
        #   _wheels: int | str = 4

        @property
        def wheels(self) -> int:
            return self._wheels

        @wheels.setter
        def wheels(self, value: int | str):
            self._wheels = int(value)


    v = Vehicle()
    print(v.wheels)  # 4
    v.wheels = '6'
    print(v.wheels)  # 6

    assert v.wheels == 6, 'Setter correctly handles type conversion'

📖 For a deeper dive, visit the documentation on `field properties`_.

.. rubric:: Generate Dataclass Schemas with CLI

Quickly generate Python dataclasses from JSON input using the ``wiz-cli`` tool:

.. code-block:: console

    $ echo '{"myFloat": "1.23", "Items": [{"created": "2021-01-01"}]}' | wiz gs - output.py

.. code-block:: python3

    from dataclasses import dataclass
    from datetime import date
    from typing import List, Union

    from dataclass_wizard import JSONWizard

    @dataclass
    class Data(JSONWizard):
        my_float: Union[float, str]
        items: List['Item']

    @dataclass
    class Item:
        created: date

📖 Check out the full CLI documentation at wiz-cli_.

JSON Marshalling
----------------

``JSONSerializable`` (aliased to ``JSONWizard``) is a Mixin_ class which
provides the following helper methods that are useful for serializing (and loading)
a dataclass instance to/from JSON, as defined by the ``AbstractJSONWizard``
interface.

.. list-table::
   :widths: 10 40 35
   :header-rows: 1

   * - Method
     - Example
     - Description
   * - ``from_json``
     - `item = Product.from_json(string)`
     - Converts a JSON string to an instance of the
       dataclass, or a list of the dataclass instances.
   * - ``from_list``
     - `list_of_item = Product.from_list(l)`
     - Converts a Python ``list`` object to a list of the
       dataclass instances.
   * - ``from_dict``
     - `item = Product.from_dict(d)`
     - Converts a Python ``dict`` object to an instance
       of the dataclass.
   * - ``to_dict``
     - `d = item.to_dict()`
     - Converts the dataclass instance to a Python ``dict``
       object that is JSON serializable.
   * - ``to_json``
     - `string = item.to_json()`
     - Converts the dataclass instance to a JSON string
       representation.
   * - ``list_to_json``
     - `string = Product.list_to_json(list_of_item)`
     - Converts a list of dataclass instances to a JSON string
       representation.

Additionally, it adds a default ``__str__`` method to subclasses, which will
pretty print the JSON representation of an object; this is quite useful for
debugging purposes. Whenever you invoke ``print(obj)`` or ``str(obj)``, for
example, it'll call this method which will format the dataclass object as
a prettified JSON string. If you prefer a ``__str__`` method to not be
added, you can pass in ``str=False`` when extending from the Mixin class
as mentioned `here <https://dataclass-wizard.readthedocs.io/en/latest/common_use_cases/skip_the_str.html>`_.

Note that the ``__repr__`` method, which is implemented by the
``dataclass`` decorator, is also available. To invoke the Python object
representation of the dataclass instance, you can instead use
``repr(obj)`` or ``f'{obj!r}'``.

To mark a dataclass as being JSON serializable (and
de-serializable), simply sub-class from ``JSONSerializable`` as shown
below. You can also extend from the aliased name ``JSONWizard``, if you
prefer to use that instead.

Check out a `more complete example`_ of using the ``JSONSerializable``
Mixin class.

No Inheritance Needed
---------------------

It is important to note that the main purpose of sub-classing from
``JSONWizard`` Mixin class is to provide helper methods like ``from_dict``
and ``to_dict``, which makes it much more convenient and easier to load or
dump your data class from and to JSON.

That is, it's meant to *complement* the usage of the ``dataclass`` decorator,
rather than to serve as a drop-in replacement for data classes, or to provide type
validation for example; there are already excellent libraries like `pydantic`_ that
provide these features if so desired.

However, there may be use cases where we prefer to do away with the class
inheritance model introduced by the Mixin class. In the interests of convenience
and also so that data classes can be used *as is*, the Dataclass
Wizard library provides the helper functions ``fromlist`` and ``fromdict``
for de-serialization, and ``asdict`` for serialization. These functions also
work recursively, so there is full support for nested dataclasses -- just as with
the class inheritance approach.

Here is an example to demonstrate the usage of these helper functions:

.. note::
  As of *v0.18.0*, the Meta config for the main dataclass will cascade down
  and be merged with the Meta config (if specified) of each nested dataclass. To
  disable this behavior, you can pass in ``recursive=False`` to the Meta config.

.. code:: python3

    from __future__ import annotations

    from dataclasses import dataclass, field
    from datetime import datetime, date

    from dataclass_wizard import fromdict, asdict, DumpMeta


    @dataclass
    class A:
        created_at: datetime
        list_of_b: list[B] = field(default_factory=list)


    @dataclass
    class B:
        my_status: int | str
        my_date: date | None = None


    source_dict = {'createdAt': '2010-06-10 15:50:00Z',
                   'List-Of-B': [
                       {'MyStatus': '200', 'my_date': '2021-12-31'}
                   ]}

    # De-serialize the JSON dictionary object into an `A` instance.
    a = fromdict(A, source_dict)

    print(repr(a))
    # A(created_at=datetime.datetime(2010, 6, 10, 15, 50, tzinfo=datetime.timezone.utc),
    #   list_of_b=[B(my_status='200', my_date=datetime.date(2021, 12, 31))])

    # Set an optional dump config for the main dataclass, for example one which
    # converts converts date and datetime objects to a unix timestamp (as an int)
    #
    # Note that `recursive=True` is the default, so this Meta config will be
    # merged with the Meta config (if specified) of each nested dataclass.
    DumpMeta(marshal_date_time_as='TIMESTAMP',
             key_transform='SNAKE',
             # Finally, apply the Meta config to the main dataclass.
             ).bind_to(A)

    # Serialize the `A` instance to a Python dict object.
    json_dict = asdict(a)

    expected_dict = {'created_at': 1276185000, 'list_of_b': [{'my_status': '200', 'my_date': 1640926800}]}

    print(json_dict)
    # Assert that we get the expected dictionary object.
    assert json_dict == expected_dict

Custom Key Mappings
-------------------

.. note::
    **Important:** The functionality for **custom key mappings** (such as JSON-to-dataclass field mappings) is being re-imagined with the introduction of **V1 Opt-in**. Enhanced support for these features is now available, improving the user experience for working with custom mappings.

    For more details, see the `Field Guide to V1 Opt-in`_ and the `V1 Alias`_ documentation.

    This change is part of the ongoing improvements in version ``v0.35.0+``, and the old functionality will no longer be maintained in future releases.

If you ever find the need to add a `custom mapping`_ of a JSON key to a dataclass
field (or vice versa), the helper function ``json_field`` -- which can be
considered an alias to ``dataclasses.field()`` -- is one approach that can
resolve this.

Example below:

.. code:: python3

    from dataclasses import dataclass

    from dataclass_wizard import JSONSerializable, json_field


    @dataclass
    class MyClass(JSONSerializable):

        my_str: str = json_field('myString1', all=True)


    # De-serialize a dictionary object with the newly mapped JSON key.
    d = {'myString1': 'Testing'}
    c = MyClass.from_dict(d)

    print(repr(c))
    # prints:
    #   MyClass(my_str='Testing')

    # Assert we get the same dictionary object when serializing the instance.
    assert c.to_dict() == d

Mapping Nested JSON Keys
------------------------

.. note::
    **Important:** The current "nested path" functionality is being re-imagined.
    Please refer to the new docs for **V1 Opt-in** features, which introduce enhanced support for these use
    cases. For more details, see the `Field Guide to V1 Opt-in`_ and the `V1 Alias`_ documentation.

    This change is part of the ongoing improvements in version ``v0.35.0+``, and the old functionality will no longer be maintained in future releases.

The ``dataclass-wizard`` library allows you to map deeply nested JSON keys to dataclass fields using custom path notation. This is ideal for handling complex or non-standard JSON structures.

You can specify paths to JSON keys with the ``KeyPath`` or ``path_field`` helpers. For example, the deeply nested key ``data.items.myJSONKey`` can be mapped to a dataclass field, such as ``my_str``:

.. code:: python3

    from dataclasses import dataclass
    from dataclass_wizard import path_field, JSONWizard

    @dataclass
    class MyData(JSONWizard):
        my_str: str = path_field('data.items.myJSONKey', default="default_value")

    input_dict = {'data': {'items': {'myJSONKey': 'Some value'}}}
    data_instance = MyData.from_dict(input_dict)
    print(data_instance.my_str)  # Output: 'Some value'

Custom Paths for Complex JSON
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

You can now use `custom paths to access nested keys`_ and map them to specific fields, even when keys contain special characters or follow non-standard conventions.

Example with nested and complex keys:

.. code:: python3

    from dataclasses import dataclass
    from typing import Annotated
    from dataclass_wizard import JSONWizard, path_field, KeyPath


    @dataclass
    class NestedData(JSONWizard):
        my_str: str = path_field('data[0].details["key with space"]', default="default_value")
        my_int: Annotated[int, KeyPath('data[0].items[3.14].True')] = 0


    input_dict = {
        'data': [
            {
                'details': {'key with space': 'Another value'},
                'items': {3.14: {True: "42"}}
            }
        ]
    }

    # Deserialize JSON to dataclass
    data = NestedData.from_dict(input_dict)
    print(data.my_str)  # Output: 'Another value'

    # Serialize back to JSON
    output_dict = data.to_dict()
    print(output_dict)  # {'data': {0: {'details': {'key with space': 'Another value'}, 'items': {3.14: {True: 42}}}}}

    # Verify data consistency
    assert data == NestedData.from_dict(output_dict)

    # Handle empty input gracefully
    data = NestedData.from_dict({'data': []})
    print(repr(data))  # NestedData(my_str='default_value', my_int=0)

Extending from ``Meta``
-----------------------

Looking to change how ``date`` and ``datetime`` objects are serialized to JSON? Or
prefer that field names appear in *snake case* when a dataclass instance is serialized?

The inner ``Meta`` class allows easy configuration of such settings, as
shown below; and as a nice bonus, IDEs should be able to assist with code completion
along the way.

.. note::
  As of *v0.18.0*, the Meta config for the main dataclass will cascade down
  and be merged with the Meta config (if specified) of each nested dataclass. To
  disable this behavior, you can pass in ``recursive=False`` to the Meta config.

.. code:: python3

    from dataclasses import dataclass
    from datetime import date

    from dataclass_wizard import JSONWizard
    from dataclass_wizard.enums import DateTimeTo


    @dataclass
    class MyClass(JSONWizard):

        class _(JSONWizard.Meta):
            marshal_date_time_as = DateTimeTo.TIMESTAMP
            key_transform_with_dump = 'SNAKE'

        my_str: str
        my_date: date


    data = {'my_str': 'test', 'myDATE': '2010-12-30'}

    c = MyClass.from_dict(data)

    print(repr(c))
    # prints:
    #   MyClass(my_str='test', my_date=datetime.date(2010, 12, 30))

    string = c.to_json()
    print(string)
    # prints:
    #   {"my_str": "test", "my_date": 1293685200}

Other Uses for ``Meta``
~~~~~~~~~~~~~~~~~~~~~~~

Here are a few additional use cases for the inner ``Meta`` class. Note that
a full list of available settings can be found in the `Meta`_ section in the docs.

Debug Mode
##########

.. admonition:: **Added in v0.28.0**

   There is now `Easier Debug Mode`_.

Enables additional (more verbose) log output. For example, a message can be
logged whenever an unknown JSON key is encountered when
``from_dict`` or ``from_json`` is called.

This also results in more helpful error messages during the JSON load
(de-serialization) process, such as when values are an invalid type --
i.e. they don't match the annotation for the field. This can be particularly
useful for debugging purposes.

.. note::
  There is a minor performance impact when DEBUG mode is enabled;
  for that reason, I would personally advise against enabling
  this in a *production* environment.

Handle Unknown JSON Keys
########################

The default behavior is to ignore any unknown or extraneous JSON keys that are
encountered when ``from_dict`` or ``from_json`` is called, and emit a "warning"
which is visible when *debug* mode is enabled (and logging is properly configured).
An unknown key is one that does not have a known mapping to a dataclass field.

However, we can also raise an error in such cases if desired. The below
example demonstrates a use case where we want to raise an error when
an unknown JSON key is encountered in the  *load* (de-serialization) process.

.. code:: python3

    import logging
    from dataclasses import dataclass

    from dataclass_wizard import JSONWizard
    from dataclass_wizard.errors import UnknownJSONKey

    # Sets up application logging if we haven't already done so
    logging.basicConfig(level='DEBUG')


    @dataclass
    class Container(JSONWizard):

        class _(JSONWizard.Meta):
            # True to enable Debug mode for additional (more verbose) log output.
            #
            # Pass in a `str` to `int` to set the minimum log level:
            #   logging.getLogger('dataclass_wizard').setLevel('INFO')
            debug_enabled = logging.INFO
            # True to raise an class:`UnknownJSONKey` when an unmapped JSON key is
            # encountered when `from_dict` or `from_json` is called. Note that by
            # default, this is also recursively applied to any nested dataclasses.
            raise_on_unknown_json_key = True

        element: 'MyElement'


    @dataclass
    class MyElement:
        my_str: str
        my_float: float


    d = {
        'element': {
            'myStr': 'string',
            'my_float': '1.23',
            # Notice how this key is not mapped to a known dataclass field!
            'my_bool': 'Testing'
        }
    }

    # Try to de-serialize the dictionary object into a `MyClass` object.
    try:
        c = Container.from_dict(d)
    except UnknownJSONKey as e:
        print('Received error:', type(e).__name__)
        print('Class:', e.class_name)
        print('Unknown JSON key:', e.json_key)
        print('JSON object:', e.obj)
        print('Known Fields:', e.fields)
    else:
        print('Successfully de-serialized the JSON object.')
        print(repr(c))

See the section on `Handling Unknown JSON Keys`_ for more info.

Save or "Catch-All" Unknown JSON Keys
######################################

When calling ``from_dict`` or ``from_json``, any unknown or extraneous JSON keys
that are not mapped to fields in the dataclass are typically ignored or raise an error.
However, you can capture these undefined keys in a catch-all field of type ``CatchAll``,
allowing you to handle them as needed later.

For example, suppose you have the following dictionary::

    dump_dict = {
        "endpoint": "some_api_endpoint",
        "data": {"foo": 1, "bar": "2"},
        "undefined_field_name": [1, 2, 3]
    }

You can save the undefined keys in a catch-all field and process them later.
Simply define a field of type ``CatchAll`` in your dataclass. This field will act
as a dictionary to store any unmapped keys and their values. If there are no
undefined keys, the field will default to an empty dictionary.

.. code:: python

    from dataclasses import dataclass
    from typing import Any
    from dataclass_wizard import CatchAll, JSONWizard

    @dataclass
    class UnknownAPIDump(JSONWizard):
        endpoint: str
        data: dict[str, Any]
        unknown_things: CatchAll

    dump_dict = {
        "endpoint": "some_api_endpoint",
        "data": {"foo": 1, "bar": "2"},
        "undefined_field_name": [1, 2, 3]
    }

    dump = UnknownAPIDump.from_dict(dump_dict)
    print(f'{dump!r}')
    # > UnknownAPIDump(endpoint='some_api_endpoint', data={'foo': 1, 'bar': '2'},
    #       unknown_things={'undefined_field_name': [1, 2, 3]})

    print(dump.to_dict())
    # > {'endpoint': 'some_api_endpoint', 'data': {'foo': 1, 'bar': '2'}, 'undefined_field_name': [1, 2, 3]}

.. note::
    - When using a "catch-all" field, it is strongly recommended to define exactly **one** field of type ``CatchAll`` in the dataclass.

    - ``LetterCase`` transformations do not apply to keys stored in the ``CatchAll`` field; the keys remain as they are provided.

    - If you specify a default (or a default factory) for the ``CatchAll`` field, such as
      ``unknown_things: CatchAll = None``, the default value will be used instead of an
      empty dictionary when no undefined parameters are present.

    - The ``CatchAll`` functionality is guaranteed only when using ``from_dict`` or ``from_json``.
      Currently, unknown keyword arguments passed to ``__init__`` will not be written to a ``CatchAll`` field.

Date and Time with Custom Patterns
----------------------------------

.. tip::
    As of **v0.35.0** with V1 Opt-in, Dataclass Wizard now supports timezone-aware and UTC ``datetime``
    and ``time`` patterns, as well as multiple pattern strings (i.e. multiple `custom formats`) for greater
    flexibility in pattern matching. These features are **not** available in the current ``v0.*`` versions.

    The new features include:

    - Timezone-aware ``datetime`` and ``time`` patterns.
    - UTC ``datetime`` and ``time`` patterns.
    - Multiple `custom formats`_ for a single field, providing more control over pattern matching.

    For more details and examples on how to use these new features, refer to the `V1 Opt-in documentation for Patterned Date and Time`_.

As of **v0.20.0**, date and time strings in `custom formats`_ can be de-serialized using the ``DatePattern``,
``TimePattern``, and ``DateTimePattern`` type annotations, which represent patterned ``date``, ``time``, and
``datetime`` objects, respectively.

Internally, these annotations use ``datetime.strptime`` with the specified format and the ``fromisoformat()``
method for ISO-8601 formatted strings. All date and time values are still serialized to ISO format strings by
default. For more information, refer to the `Patterned Date and Time`_ section in the documentation.

Here is an example demonstrating how to use these annotations:

.. code-block:: python3

    from dataclasses import dataclass
    from datetime import time, datetime
    from typing import Annotated

    from dataclass_wizard import fromdict, asdict, DatePattern, TimePattern, Pattern


    @dataclass
    class MyClass:
        # Custom format for date (Month-Year)
        date_field: DatePattern['%m-%Y']
        # Custom format for datetime (Month/Day/Year Hour.Minute.Second)
        dt_field: Annotated[datetime, Pattern('%m/%d/%y %H.%M.%S')]
        # Custom format for time (Hour:Minute)
        time_field1: TimePattern['%H:%M']
        # Custom format for a list of times (12-hour format with AM/PM)
        time_field2: Annotated[list[time], Pattern('%I:%M %p')]


    data = {'date_field': '12-2022',
            'time_field1': '15:20',
            'dt_field': '1/02/23 02.03.52',
            'time_field2': ['1:20 PM', '12:30 am']}

    class_obj = fromdict(MyClass, data)

    # All annotated fields de-serialize to date, time, or datetime objects, as shown.
    print(class_obj)
    # MyClass(date_field=datetime.date(2022, 12, 1), dt_field=datetime.datetime(2023, 1, 2, 2, 3, 52),
    #         time_field1=datetime.time(15, 20), time_field2=[datetime.time(13, 20), datetime.time(0, 30)])

    # All date/time fields are serialized as ISO-8601 format strings by default.
    print(asdict(class_obj))
    # {'dateField': '2022-12-01', 'dtField': '2023-01-02T02:03:52',
    #  'timeField1': '15:20:00', 'timeField2': ['13:20:00', '00:30:00']}

    # The patterned date/times can be de-serialized back after serialization, which will be faster than
    # re-parsing the custom patterns!
    assert class_obj == fromdict(MyClass, asdict(class_obj))

Recursive Types and Dataclasses with Cyclic References
------------------------------------------------------

Prior to version **0.27.0**, dataclasses with cyclic references
or self-referential structures were not supported. This
limitation is shown in the following toy example:

.. code:: python3

    from dataclasses import dataclass

    @dataclass
    class A:
        a: 'A | None' = None

    a = A(a=A(a=A(a=A())))

This was a `longstanding issue`_, but starting with ``v0.27.0``, Dataclass Wizard now supports
recursive dataclasses, including cyclic references.

The example below demonstrates recursive
dataclasses with cyclic dependencies, following the pattern ``A -> B -> A -> B``.
For more details, see the `Cyclic or "Recursive" Dataclasses`_ section in the documentation.

.. code:: python3

    from __future__ import annotations  # This can be removed in Python 3.10+

    from dataclasses import dataclass
    from dataclass_wizard import JSONWizard

    @dataclass
    class A(JSONWizard):
        class _(JSONWizard.Meta):
            # Enable support for self-referential / recursive dataclasses
            recursive_classes = True

        b: 'B | None' = None


    @dataclass
    class B:
        a: A | None = None

    # Confirm that `from_dict` with a recursive, self-referential
    # input `dict` works as expected.
    a = A.from_dict({'b': {'a': {'b': {'a': None}}}})

    assert a == A(b=B(a=A(b=B())))

Starting with version **0.34.0**, recursive types are supported *out of the box* (OOTB) with ``v1`` opt-in,
removing the need for any ``Meta`` settings like ``recursive_classes = True``.

This makes working with recursive dataclasses even easier and more streamlined. In addition, recursive types
are now supported for the following Python type constructs:

- NamedTuple_
- TypedDict_
- Union_
- Literal_
- Nested dataclasses_
- `Type aliases`_ (introduced in Python 3.12+)

.. _NamedTuple: https://docs.python.org/3/library/typing.html#typing.NamedTuple
.. _TypedDict: https://docs.python.org/3/library/typing.html#typing.TypedDict
.. _Union: https://docs.python.org/3/library/typing.html#typing.Union
.. _Literal: https://docs.python.org/3/library/typing.html#typing.Literal
.. _Type aliases: https://docs.python.org/3/library/typing.html#type-aliases

Example Usage
~~~~~~~~~~~~~

Recursive types allow handling complex nested data structures, such as deeply nested JSON objects or lists.
With ``v0.34.0`` of Dataclass Wizard, de/serializing these structures becomes seamless
and more intuitive.

Recursive ``Union``
###################

.. code-block:: python3

    from dataclasses import dataclass
    from dataclass_wizard import JSONWizard

    # For Python 3.9, use this `Union` approach:
    from typing_extensions import TypeAlias
    JSON: TypeAlias = 'str | int | float | bool | dict[str, JSON] | list[JSON] | None'

    # For Python 3.10 and above, use this simpler approach:
    # JSON = str | int | float | bool | dict[str, 'JSON'] | list['JSON'] | None

    # For Python 3.12+, you can use the `type` statement:
    # type JSON = str | int | float | bool | dict[str, JSON] | list[JSON] | None

    @dataclass
    class MyTestClass(JSONWizard):

        class _(JSONWizard.Meta):
            v1 = True

        name: str
        meta: str
        msg: JSON

    x = MyTestClass.from_dict(
        {
            "name": "name",
            "meta": "meta",
            "msg": [{"x": {"x": [{"x": ["x", 1, 1.0, True, None]}]}}],
        }
    )
    assert x == MyTestClass(
        name="name",
        meta="meta",
        msg=[{"x": {"x": [{"x": ["x", 1, 1.0, True, None]}]}}],
    )

.. note::
   The ``type`` statement in Python 3.12+ simplifies type alias definitions by avoiding string annotations for recursive references.

Recursive ``Union`` with Nested ``dataclasses``
###############################################

.. code-block:: python3

    from dataclasses import dataclass, field
    from dataclass_wizard import JSONWizard

    @dataclass
    class A(JSONWizard):

        class _(JSONWizard.Meta):
            v1 = True

        value: int
        nested: 'B'
        next: 'A | None' = None


    @dataclass
    class B:
        items: list[A] = field(default_factory=list)


    x = A.from_dict(
        {
            "value": 1,
            "next": {"value": 2, "next": None, "nested": {}},
            "nested": {"items": [{"value": 3, "nested": {}}]},
        }
    )
    assert x == A(
        value=1,
        next=A(value=2, next=None, nested=B(items=[])),
        nested=B(items=[A(value=3, nested=B())]),
    )

.. note::
   Nested ``dataclasses`` are particularly useful for representing hierarchical structures, such as trees or graphs, in a readable and maintainable way.

Official References
~~~~~~~~~~~~~~~~~~~

For more information, see:

- `Typing in Python <https://docs.python.org/3/library/typing.html>`_
- `PEP 695: Type Syntax <https://peps.python.org/pep-0695/>`_

These examples illustrate the power of recursive types in simplifying complex data structures while leveraging the functionality of ``dataclass-wizard``.

Dataclasses in ``Union`` Types
------------------------------

The ``dataclass-wizard`` library fully supports declaring dataclass models in
`Union`_ types, such as ``list[Wizard | Archer | Barbarian]``.

Starting from *v0.19.0*, the library introduces two key features:
- **Auto-generated tags** for dataclass models (based on class names).
- A customizable **tag key** (default: ``__tag__``) that identifies the model in JSON.

These options are controlled by the ``auto_assign_tags`` and ``tag_key`` attributes in the ``Meta`` config.

For example, if a JSON object looks like ``{"type": "A", ...}``, you can set ``tag_key = "type"`` to automatically deserialize it into the appropriate class, like `A`.

Let's start out with an example, which aims to demonstrate the simplest usage of
dataclasses in ``Union`` types. For more info, check out the
`Dataclasses in Union Types`_ section in the docs.

.. code:: python3

    from __future__ import annotations

    from dataclasses import dataclass
    from dataclass_wizard import JSONWizard


    @dataclass
    class Container(JSONWizard):

        class Meta(JSONWizard.Meta):
            tag_key = 'type'
            auto_assign_tags = True

        objects: list[A | B | C]


    @dataclass
    class A:
        my_int: int
        my_bool: bool = False


    @dataclass
    class B:
        my_int: int
        my_bool: bool = True


    @dataclass
    class C:
        my_str: str


    data = {
        'objects': [
            {'type': 'A', 'my_int': 42},
            {'type': 'C', 'my_str': 'hello world'},
            {'type': 'B', 'my_int': 123},
            {'type': 'A', 'my_int': 321, 'myBool': True}
        ]
    }

    c = Container.from_dict(data)
    print(repr(c))

    # Output:
    # Container(objects=[A(my_int=42, my_bool=False),
    #                    C(my_str='hello world'),
    #                    B(my_int=123, my_bool=True),
    #                    A(my_int=321, my_bool=True)])

    print(c.to_dict())

    # True
    assert c == c.from_json(c.to_json())

Supercharged ``Union`` Parsing
------------------------------

**What about untagged dataclasses in** ``Union`` **types or** ``|`` **syntax?** With the major release **V1** opt-in, ``dataclass-wizard`` supercharges *Union* parsing, making it intuitive and flexible, even without tags.

This is especially useful for collections like ``list[Wizard]`` or when tags (discriminators) are not feasible.

To enable this feature, opt in to **v1** using the ``Meta`` settings. For details, see the `Field Guide to V1 Opt-in`_.

.. code-block:: python3

    from __future__ import annotations  # Remove in Python 3.10+

    from dataclasses import dataclass
    from typing import Literal

    from dataclass_wizard import JSONWizard

    @dataclass
    class MyClass(JSONWizard):

        class _(JSONWizard.Meta):
            v1 = True  # Enable v1 opt-in
            v1_unsafe_parse_dataclass_in_union = True

        literal_or_float: Literal['Auto'] | float
        entry: int | MoreDetails
        collection: list[MoreDetails | int]

    @dataclass
    class MoreDetails:
        arg: str

    # OK: Union types work seamlessly
    c = MyClass.from_dict({
        "literal_or_float": 1.23,
        "entry": 123,
        "collection": [{"arg": "test"}]
    })
    print(repr(c))
    #> MyClass(literal_or_float=1.23, entry=123, collection=[MoreDetails(arg='test')])

    # OK: Handles primitive and dataclass parsing
    c = MyClass.from_dict({
        "literal_or_float": "Auto",
        "entry": {"arg": "example"},
        "collection": [123]
    })
    print(repr(c))
    #> MyClass(literal_or_float='Auto', entry=MoreDetails(arg='example'), collection=[123])

Conditional Field Skipping
--------------------------

.. admonition:: **Added in v0.30.0**

    Dataclass Wizard introduces `conditional skipping`_ to omit fields during JSON serialization based on user-defined conditions. This feature works seamlessly with:

    - **Global rules** via ``Meta`` settings.
    - **Per-field controls** using ``SkipIf()`` `annotations`_.
    - **Field wrappers** for maximum flexibility.

Quick Examples
~~~~~~~~~~~~~~

1. **Globally Skip Fields Matching a Condition**

  Define a global skip rule using ``Meta.skip_if``:

  .. code-block:: python3

    from dataclasses import dataclass
    from dataclass_wizard import JSONWizard, IS_NOT


    @dataclass
    class Example(JSONWizard):
        class _(JSONWizard.Meta):
            skip_if = IS_NOT(True)  # Skip fields if the value is not `True`

        my_bool: bool
        my_str: 'str | None'


    print(Example(my_bool=True, my_str=None).to_dict())
    # Output: {'myBool': True}

2. **Skip Defaults Based on a Condition**

  Skip fields with default values matching a specific condition using ``Meta.skip_defaults_if``:

  .. code-block:: python3

    from __future__ import annotations  # Can remove in PY 3.10+

    from dataclasses import dataclass
    from dataclass_wizard import JSONPyWizard, IS


    @dataclass
    class Example(JSONPyWizard):
        class _(JSONPyWizard.Meta):
            skip_defaults_if = IS(None)  # Skip default `None` values.

        str_with_no_default: str | None
        my_str: str | None = None
        my_bool: bool = False


    print(Example(str_with_no_default=None, my_str=None).to_dict())
    #> {'str_with_no_default': None, 'my_bool': False}


  .. note::
      Setting ``skip_defaults_if`` also enables ``skip_defaults=True`` automatically.

3. **Per-Field Conditional Skipping**

  Apply skip rules to specific fields with `annotations`_ or ``skip_if_field``:

  .. code-block:: python3

    from __future__ import annotations  # can be removed in Python 3.10+

    from dataclasses import dataclass
    from typing import Annotated

    from dataclass_wizard import JSONWizard, SkipIfNone, skip_if_field, EQ


    @dataclass
    class Example(JSONWizard):
        my_str: Annotated[str | None, SkipIfNone]  # Skip if `None`.
        other_str: str | None = skip_if_field(EQ(''), default=None)  # Skip if empty.

    print(Example(my_str=None, other_str='').to_dict())
    # Output: {}

4. **Skip Fields Based on Truthy or Falsy Values**

   Use the ``IS_TRUTHY`` and ``IS_FALSY`` helpers to conditionally skip fields based on their truthiness:

   .. code-block:: python3

    from dataclasses import dataclass, field
    from dataclass_wizard import JSONWizard, IS_FALSY


    @dataclass
    class ExampleWithFalsy(JSONWizard):
        class _(JSONWizard.Meta):
            skip_if = IS_FALSY()  # Skip fields if they evaluate as "falsy".

        my_bool: bool
        my_list: list = field(default_factory=list)
        my_none: None = None

    print(ExampleWithFalsy(my_bool=False, my_list=[], my_none=None).to_dict())
    #> {}

.. note::

   *Special Cases*

   - **SkipIfNone**: Alias for ``SkipIf(IS(None))``, skips fields with a value of ``None``.
   - **Condition Helpers**:

     - ``IS``, ``IS_NOT``: Identity checks.
     - ``EQ``, ``NE``, ``LT``, ``LE``, ``GT``, ``GE``: Comparison operators.
     - ``IS_TRUTHY``, ``IS_FALSY``: Skip fields based on truthy or falsy values.

   Combine these helpers for flexible serialization rules!

.. _conditional skipping: https://dataclass-wizard.readthedocs.io/en/latest/common_use_cases/serialization_options.html#skip-if-functionality

Serialization Options
---------------------

The following parameters can be used to fine-tune and control how the serialization of a
dataclass instance to a Python ``dict`` object or JSON string is handled.

Skip Defaults
~~~~~~~~~~~~~

A common use case is skipping fields with default values - based on the ``default``
or ``default_factory`` argument to ``dataclasses.field`` - in the serialization
process.

The attribute ``skip_defaults`` in the inner ``Meta`` class can be enabled, to exclude
such field values from serialization.The ``to_dict`` method (or the ``asdict`` helper
function) can also be passed an ``skip_defaults`` argument, which should have the same
result. An example of both these approaches is shown below.

.. code:: python3

    from collections import defaultdict
    from dataclasses import field, dataclass

    from dataclass_wizard import JSONWizard


    @dataclass
    class MyClass(JSONWizard):

        class _(JSONWizard.Meta):
            skip_defaults = True

        my_str: str
        other_str: str = 'any value'
        optional_str: str = None
        my_list: list[str] = field(default_factory=list)
        my_dict: defaultdict[str, list[float]] = field(
            default_factory=lambda: defaultdict(list))


    print('-- Load (Deserialize)')
    c = MyClass('abc')
    print(f'Instance: {c!r}')

    print('-- Dump (Serialize)')
    string = c.to_json()
    print(string)

    assert string == '{"myStr": "abc"}'

    print('-- Dump (with `skip_defaults=False`)')
    print(c.to_dict(skip_defaults=False))

Exclude Fields
~~~~~~~~~~~~~~

You can also exclude specific dataclass fields (and their values) from the serialization
process. There are two approaches that can be used for this purpose:

* The argument ``dump=False`` can be passed in to the ``json_key`` and ``json_field``
  helper functions. Note that this is a more permanent option, as opposed to the one
  below.

* The ``to_dict`` method (or the ``asdict`` helper function ) can be passed
  an ``exclude`` argument, containing a list of one or more dataclass field names
  to exclude from the serialization process.

Additionally, here is an example to demonstrate usage of both these approaches:

.. code:: python3

    from dataclasses import dataclass
    from typing import Annotated

    from dataclass_wizard import JSONWizard, json_key, json_field


    @dataclass
    class MyClass(JSONWizard):

        my_str: str
        my_int: int
        other_str: Annotated[str, json_key('AnotherStr', dump=False)]
        my_bool: bool = json_field('TestBool', dump=False)


    data = {'MyStr': 'my string',
            'myInt': 1,
            'AnotherStr': 'testing 123',
            'TestBool': True}

    print('-- From Dict')
    c = MyClass.from_dict(data)
    print(f'Instance: {c!r}')

    # dynamically exclude the `my_int` field from serialization
    additional_exclude = ('my_int',)

    print('-- To Dict')
    out_dict = c.to_dict(exclude=additional_exclude)
    print(out_dict)

    assert out_dict == {'myStr': 'my string'}

``Environ`` Magic
-----------------

Easily map environment variables to Python dataclasses with ``EnvWizard``:

.. code-block:: python3

    import os
    from dataclass_wizard import EnvWizard

    # Set up environment variables
    os.environ.update({
        'APP_NAME': 'Env Wizard',
        'MAX_CONNECTIONS': '10',
        'DEBUG_MODE': 'true'
    })

    # Define dataclass using EnvWizard
    class AppConfig(EnvWizard):
        app_name: str
        max_connections: int
        debug_mode: bool

    # Load config from environment variables
    config = AppConfig()
    print(config.app_name)    #> Env Wizard
    print(config.debug_mode)  #> True
    assert config.max_connections == 10

    # Override with keyword arguments
    config = AppConfig(app_name='Dataclass Wizard Rocks!', debug_mode='false')
    print(config.app_name)    #> Dataclass Wizard Rocks!
    assert config.debug_mode is False

.. note::
    ``EnvWizard`` simplifies environment variable mapping with type validation, ``.env`` file support, and secret file handling (file names become keys).

    *Key Features*:

    - **Auto Parsing**: Supports complex types and nested structures.
    - **Configurable**: Customize variable names, prefixes, and dotenv files.
    - **Validation**: Errors for missing or malformed variables.

    📖 `Full Documentation <https://dataclass-wizard.readthedocs.io/en/latest/env_magic.html>`_

Advanced Example: Dynamic Prefix Handling
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

``EnvWizard`` supports dynamic prefix application, ideal for customizable environments:

.. code-block:: python3

    import os
    from dataclass_wizard import EnvWizard, env_field

    # Define dataclass with custom prefix support
    class AppConfig(EnvWizard):

        class _(EnvWizard.Meta):
            env_prefix = 'APP_'  # Default prefix for env vars

        name: str = env_field('A_NAME')  # Looks for `APP_A_NAME` by default
        debug: bool

    # Set environment variables
    os.environ['CUSTOM_A_NAME'] = 'Test!'
    os.environ['CUSTOM_DEBUG'] = 'yes'

    # Apply a dynamic prefix at runtime
    config = AppConfig(_env_prefix='CUSTOM_')  # Looks for `CUSTOM_A_NAME` and `CUSTOM_DEBUG`

    print(config)
    # > AppConfig(name='Test!', debug=True)

Field Properties
----------------

The Python ``dataclasses`` library has some `key limitations`_
with how it currently handles properties and default values.

The ``dataclass-wizard`` package natively provides support for using
field properties with default values in dataclasses. The main use case
here is to assign an initial value to the field property, if one is not
explicitly passed in via the constructor method.

To use it, simply import
the ``property_wizard`` helper function, and add it as a metaclass on
any dataclass where you would benefit from using field properties with
default values. The metaclass also pairs well with the ``JSONSerializable``
mixin class.

For more examples and important how-to's on properties with default values,
refer to the `Using Field Properties`_ section in the documentation.

What's New in v1.0
------------------

.. admonition:: Opt-in for v1 Now Available

   The early opt-in for **v1** is now available with enhanced features, including intuitive ``Union`` parsing and optimized performance. To enable this,
   set ``v1=True`` in your ``Meta`` settings.

   For more details and migration guidance, see the `Field Guide to V1 Opt-in`_.

.. warning:: *Important Changes in v1.0*

    - **Default Key Transformation Update**

      Starting with **v1.0.0**, the default key transformation for JSON serialization
      will change to keep keys *as-is* instead of converting them to ``camelCase``.

      **New Default Behavior**:
      The default setting for key transformation will be ``key_transform='NONE'``.

      **How to Prepare**:
      You can enforce this behavior immediately by using the ``JSONPyWizard`` helper, as shown below:

      .. code-block:: python3

            from dataclasses import dataclass
            from dataclass_wizard import JSONPyWizard

            @dataclass
            class MyModel(JSONPyWizard):
                my_field: str

            print(MyModel(my_field="value").to_dict())
            # Output: {'my_field': 'value'}

    - **Default __str__() Behavior Change**

      Starting with **v1.0.0**, we no longer pretty-print the serialized JSON value with keys in ``camelCase``.
      Instead, we now use the ``pprint`` module to handle serialization formatting.

      **New Default Behavior**:
      The ``__str__()`` method in the ``JSONWizard`` class will use ``pprint`` by default.

      **How to Prepare**:
      You can immediately test this new behavior using the ``JSONPyWizard`` helper, as demonstrated below:

      .. code-block:: python3

            from dataclasses import dataclass
            from dataclass_wizard import JSONWizard, JSONPyWizard

            @dataclass
            class CurrentModel(JSONWizard):
                my_field: str

            @dataclass
            class NewModel(JSONPyWizard):
                my_field: str

            print(CurrentModel(my_field="value"))
            #> {
            #   "myField": "value"
            # }

            print(NewModel(my_field="value"))
            #> NewModel(my_field='value')

    - **Float to Int Conversion Change**
      Starting with **v1.0**, floats or float strings with fractional parts (e.g., ``123.4`` or ``"123.4"``) will no longer be silently converted to integers. Instead, they will raise an error. However, floats without fractional parts (e.g., ``3.0`` or ``"3.0"``) will continue to convert to integers as before.

      **How to Prepare**:
      You can opt in to **v1** via ``v1=True`` to test this behavior right now. Additionally, to ensure compatibility with the new behavior:

      - Use ``float`` annotations for fields that may include fractional values.
      - Review your data to avoid passing fractional values (e.g., ``123.4``) to fields annotated as ``int``.
      - Update tests or logic that depend on the current rounding behavior.

      .. code-block:: python3

            from dataclasses import dataclass
            from dataclass_wizard import JSONPyWizard

            @dataclass
            class Test(JSONPyWizard):
                class _(JSONPyWizard.Meta):
                    v1 = True

                list_of_int: list[int]

            input_dict = {'list_of_int': [1, '2.0', '3.', -4, '-5.00', '6', '-7']}
            t = Test.from_dict(input_dict)
            print(t)  #> Test(list_of_int=[1, 2, 3, -4, -5, 6, -7])

            # ERROR!
            _ = Test.from_dict({'list_of_int': [123.4]})

Contributing
------------

Contributions are welcome! Open a pull request to fix a bug, or `open an issue`_
to discuss a new feature or change.

Check out the `Contributing`_ section in the docs for more info.

TODOs
-----

All feature ideas or suggestions for future consideration, have been currently added
`as milestones`_ in the project's GitHub repo.

Credits
-------

This package was created with Cookiecutter_ and the `rnag/cookiecutter-pypackage`_ project template.

.. _Read The Docs: https://dataclass-wizard.readthedocs.io
.. _Installation: https://dataclass-wizard.readthedocs.io/en/latest/installation.html
.. _Cookiecutter: https://github.com/cookiecutter/cookiecutter
.. _`rnag/cookiecutter-pypackage`: https://github.com/rnag/cookiecutter-pypackage
.. _`Contributing`: https://dataclass-wizard.readthedocs.io/en/latest/contributing.html
.. _`open an issue`: https://github.com/rnag/dataclass-wizard/issues
.. _`JSONPyWizard`: https://dataclass-wizard.readthedocs.io/en/latest/common_use_cases/wizard_mixins.html#jsonpywizard
.. _`EnvWizard`: https://dataclass-wizard.readthedocs.io/en/latest/common_use_cases/wizard_mixins.html#envwizard
.. _`on EnvWizard`: https://dataclass-wizard.readthedocs.io/en/latest/env_magic.html
.. _`JSONListWizard`: https://dataclass-wizard.readthedocs.io/en/latest/common_use_cases/wizard_mixins.html#jsonlistwizard
.. _`JSONFileWizard`: https://dataclass-wizard.readthedocs.io/en/latest/common_use_cases/wizard_mixins.html#jsonfilewizard
.. _`TOMLWizard`: https://dataclass-wizard.readthedocs.io/en/latest/common_use_cases/wizard_mixins.html#tomlwizard
.. _`YAMLWizard`: https://dataclass-wizard.readthedocs.io/en/latest/common_use_cases/wizard_mixins.html#yamlwizard
.. _`Container`: https://dataclass-wizard.readthedocs.io/en/latest/dataclass_wizard.html#dataclass_wizard.Container
.. _`Supported Types`: https://dataclass-wizard.readthedocs.io/en/latest/overview.html#supported-types
.. _`Mixin`: https://stackoverflow.com/a/547714/10237506
.. _`Meta`: https://dataclass-wizard.readthedocs.io/en/latest/common_use_cases/meta.html
.. _`pydantic`: https://pydantic-docs.helpmanual.io/
.. _`Using Field Properties`: https://dataclass-wizard.readthedocs.io/en/latest/using_field_properties.html
.. _`field properties`: https://dataclass-wizard.readthedocs.io/en/latest/using_field_properties.html
.. _`custom mapping`: https://dataclass-wizard.readthedocs.io/en/latest/common_use_cases/custom_key_mappings.html
.. _`wiz-cli`: https://dataclass-wizard.readthedocs.io/en/latest/wiz_cli.html
.. _`key limitations`: https://florimond.dev/en/posts/2018/10/reconciling-dataclasses-and-properties-in-python/
.. _`more complete example`: https://dataclass-wizard.readthedocs.io/en/latest/examples.html#a-more-complete-example
.. _custom formats: https://docs.python.org/3/library/datetime.html#strftime-and-strptime-format-codes
.. _`Patterned Date and Time`: https://dataclass-wizard.readthedocs.io/en/latest/common_use_cases/patterned_date_time.html
.. _Union: https://docs.python.org/3/library/typing.html#typing.Union
.. _`Dataclasses in Union Types`: https://dataclass-wizard.readthedocs.io/en/latest/common_use_cases/dataclasses_in_union_types.html
.. _`Cyclic or "Recursive" Dataclasses`: https://dataclass-wizard.readthedocs.io/en/latest/common_use_cases/cyclic_or_recursive_dataclasses.html
.. _as milestones: https://github.com/rnag/dataclass-wizard/milestones
.. _longstanding issue: https://github.com/rnag/dataclass-wizard/issues/62
.. _Easier Debug Mode: https://dataclass-wizard.readthedocs.io/en/latest/common_use_cases/easier_debug_mode.html
.. _Handling Unknown JSON Keys: https://dataclass-wizard.readthedocs.io/en/latest/common_use_cases/handling_unknown_json_keys.html
.. _custom paths to access nested keys: https://dataclass-wizard.readthedocs.io/en/latest/common_use_cases/nested_key_paths.html
.. _annotations: https://docs.python.org/3/library/typing.html#typing.Annotated
.. _typing: https://docs.python.org/3/library/typing.html
.. _dataclasses: https://docs.python.org/3/library/dataclasses.html
.. _V1 Opt-in documentation for Patterned Date and Time: https://dataclass-wizard.readthedocs.io/en/latest/common_use_cases/v1_patterned_date_time.html
.. _`Field Guide to V1 Opt-in`: https://github.com/rnag/dataclass-wizard/wiki/Field-Guide-to-V1-Opt%E2%80%90in
.. _V1 Alias: https://dataclass-wizard.readthedocs.io/en/latest/common_use_cases/v1_alias.html