@@ -25,6 +25,7 @@ def converter_sqlite():
2525
2626# --- Basic conversion tests ---
2727
28+
2829class TestBasicConversion :
2930 def test_single_object_postgres (self , converter_postgres ):
3031 data = json .dumps ({"name" : "Alice" , "age" : 30 , "active" : True })
@@ -36,10 +37,12 @@ def test_single_object_postgres(self, converter_postgres):
3637 assert "TRUE" in result
3738
3839 def test_array_of_objects (self , converter_postgres ):
39- data = json .dumps ([
40- {"name" : "Alice" , "age" : 30 },
41- {"name" : "Bob" , "age" : 25 },
42- ])
40+ data = json .dumps (
41+ [
42+ {"name" : "Alice" , "age" : 30 },
43+ {"name" : "Bob" , "age" : 25 },
44+ ]
45+ )
4346 result = converter_postgres .convert (data , table_name = "users" )
4447 assert "CREATE TABLE" in result
4548 assert "INSERT INTO" in result
@@ -65,6 +68,7 @@ def test_null_values(self, converter_postgres):
6568
6669# --- Dialect tests ---
6770
71+
6872class TestDialects :
6973 def test_mysql_boolean (self , converter_mysql ):
7074 data = json .dumps ({"active" : True })
@@ -97,6 +101,7 @@ def test_mysql_type_mapping(self, converter_mysql):
97101
98102# --- Nested/flatten tests ---
99103
104+
100105class TestFlatten :
101106 def test_flatten_nested_object (self ):
102107 conv = JSONToSQLConverter (dialect = Dialect .POSTGRES , flatten = True )
@@ -108,14 +113,16 @@ def test_flatten_nested_object(self):
108113
109114 def test_flatten_nested_array (self ):
110115 conv = JSONToSQLConverter (dialect = Dialect .POSTGRES , flatten = True )
111- data = json .dumps ({
112- "id" : 1 ,
113- "name" : "Alice" ,
114- "orders" : [
115- {"product" : "Widget" , "qty" : 3 },
116- {"product" : "Gadget" , "qty" : 1 },
117- ],
118- })
116+ data = json .dumps (
117+ {
118+ "id" : 1 ,
119+ "name" : "Alice" ,
120+ "orders" : [
121+ {"product" : "Widget" , "qty" : 3 },
122+ {"product" : "Gadget" , "qty" : 1 },
123+ ],
124+ }
125+ )
119126 result = conv .convert (data , table_name = "users" )
120127 assert "CREATE TABLE" in result
121128 # Should create a separate table for orders
@@ -124,6 +131,7 @@ def test_flatten_nested_array(self):
124131
125132# --- Schema-only tests ---
126133
134+
127135class TestSchemaOnly :
128136 def test_generate_schema (self , converter_postgres ):
129137 data = json .dumps ([{"name" : "Alice" , "age" : 30 }])
@@ -134,6 +142,7 @@ def test_generate_schema(self, converter_postgres):
134142
135143# --- Edge cases ---
136144
145+
137146class TestFlattenRegression :
138147 """Regression tests for flatten bugs."""
139148
@@ -145,20 +154,23 @@ def test_flatten_nested_array_column_value_count(self):
145154 row but removed from columns, causing a mismatch.
146155 """
147156 import re
157+
148158 conv = JSONToSQLConverter (dialect = Dialect .POSTGRES , flatten = True )
149- data = json .dumps ({
150- "id" : 1 ,
151- "name" : "Alice" ,
152- "orders" : [
153- {"product" : "Widget" , "qty" : 3 },
154- ],
155- })
159+ data = json .dumps (
160+ {
161+ "id" : 1 ,
162+ "name" : "Alice" ,
163+ "orders" : [
164+ {"product" : "Widget" , "qty" : 3 },
165+ ],
166+ }
167+ )
156168 result = conv .convert (data , table_name = "users" )
157169 # Parse the INSERT for the parent table
158170 for block in result .split ("\n \n " ):
159- if ' INSERT INTO' in block and '"users"' in block :
160- cols = re .search (r' \(([^)]+)\)\s*VALUES' , block )
161- vals = re .search (r' VALUES\s*\(([^)]+)\)' , block )
171+ if " INSERT INTO" in block and '"users"' in block :
172+ cols = re .search (r" \(([^)]+)\)\s*VALUES" , block )
173+ vals = re .search (r" VALUES\s*\(([^)]+)\)" , block )
162174 if cols and vals :
163175 c_count = len ([c for c in cols .group (1 ).split ("," ) if c .strip ()])
164176 v_count = len ([v for v in vals .group (1 ).split ("," ) if v .strip ()])
@@ -172,17 +184,20 @@ def test_flatten_mixed_nested_array_and_object_count(self):
172184 should produce column-value count match.
173185 """
174186 import re
187+
175188 conv = JSONToSQLConverter (dialect = Dialect .POSTGRES , flatten = True )
176- data = json .dumps ({
177- "id" : 1 ,
178- "profile" : {"age" : 30 , "city" : "NYC" },
179- "tags" : [{"name" : "dev" }],
180- })
189+ data = json .dumps (
190+ {
191+ "id" : 1 ,
192+ "profile" : {"age" : 30 , "city" : "NYC" },
193+ "tags" : [{"name" : "dev" }],
194+ }
195+ )
181196 result = conv .convert (data , table_name = "users" )
182197 for block in result .split ("\n \n " ):
183- if ' INSERT INTO' in block and '"users"' in block :
184- cols = re .search (r' \(([^)]+)\)\s*VALUES' , block )
185- vals = re .search (r' VALUES\s*\(([^)]+)\)' , block )
198+ if " INSERT INTO" in block and '"users"' in block :
199+ cols = re .search (r" \(([^)]+)\)\s*VALUES" , block )
200+ vals = re .search (r" VALUES\s*\(([^)]+)\)" , block )
186201 if cols and vals :
187202 c_count = len ([c for c in cols .group (1 ).split ("," ) if c .strip ()])
188203 v_count = len ([v for v in vals .group (1 ).split ("," ) if v .strip ()])
@@ -219,10 +234,12 @@ def test_flatten_nested_object_insert_values(self):
219234 def test_flatten_multiple_rows (self ):
220235 """Multiple objects with nested dicts should flatten consistently."""
221236 conv = JSONToSQLConverter (dialect = Dialect .POSTGRES , flatten = True )
222- data = json .dumps ([
223- {"id" : 1 , "meta" : {"color" : "red" , "size" : "M" }},
224- {"id" : 2 , "meta" : {"color" : "blue" , "size" : "L" }},
225- ])
237+ data = json .dumps (
238+ [
239+ {"id" : 1 , "meta" : {"color" : "red" , "size" : "M" }},
240+ {"id" : 2 , "meta" : {"color" : "blue" , "size" : "L" }},
241+ ]
242+ )
226243 result = conv .convert (data , table_name = "items" )
227244 assert '"meta_color"' in result
228245 assert '"meta_size"' in result
@@ -233,13 +250,15 @@ def test_flatten_multiple_rows(self):
233250 def test_flatten_deeply_nested (self ):
234251 """Deeply nested dicts should be one-level flattened (not recursive)."""
235252 conv = JSONToSQLConverter (dialect = Dialect .POSTGRES , flatten = True )
236- data = json .dumps ({
237- "id" : 1 ,
238- "location" : {
239- "city" : "NYC" ,
240- "coordinates" : {"lat" : 40.71 , "lng" : - 74.01 },
241- },
242- })
253+ data = json .dumps (
254+ {
255+ "id" : 1 ,
256+ "location" : {
257+ "city" : "NYC" ,
258+ "coordinates" : {"lat" : 40.71 , "lng" : - 74.01 },
259+ },
260+ }
261+ )
243262 result = conv .convert (data , table_name = "places" )
244263 # First-level flatten: location_city and location_coordinates
245264 assert '"location_city"' in result
@@ -261,11 +280,13 @@ def test_flatten_empty_nested_object(self):
261280 def test_flatten_with_array_and_object_mixed (self ):
262281 """Mix of nested arrays and nested objects with flatten."""
263282 conv = JSONToSQLConverter (dialect = Dialect .POSTGRES , flatten = True )
264- data = json .dumps ({
265- "id" : 1 ,
266- "profile" : {"age" : 30 , "city" : "NYC" },
267- "tags" : [{"name" : "dev" }, {"name" : "python" }],
268- })
283+ data = json .dumps (
284+ {
285+ "id" : 1 ,
286+ "profile" : {"age" : 30 , "city" : "NYC" },
287+ "tags" : [{"name" : "dev" }, {"name" : "python" }],
288+ }
289+ )
269290 result = conv .convert (data , table_name = "users" )
270291 assert '"profile_age"' in result
271292 assert '"profile_city"' in result
@@ -289,11 +310,13 @@ def test_generate_schema_with_flatten_nested_object(self):
289310 def test_generate_schema_with_flatten_mixed (self ):
290311 """Schema-only with flatten for nested arrays and objects."""
291312 conv = JSONToSQLConverter (dialect = Dialect .POSTGRES , flatten = True )
292- data = json .dumps ({
293- "id" : 1 ,
294- "profile" : {"age" : 30 },
295- "orders" : [{"product" : "Widget" , "qty" : 3 }],
296- })
313+ data = json .dumps (
314+ {
315+ "id" : 1 ,
316+ "profile" : {"age" : 30 },
317+ "orders" : [{"product" : "Widget" , "qty" : 3 }],
318+ }
319+ )
297320 result = conv .generate_schema (data , table_name = "users" )
298321 assert "CREATE TABLE" in result
299322 assert '"profile_age"' in result
@@ -308,21 +331,24 @@ def test_flatten_missing_sub_key_gets_null(self):
308331 """When one object's nested dict is missing a sub-key present in others,
309332 the missing sub-key should get NULL in the INSERT row."""
310333 import re
334+
311335 conv = JSONToSQLConverter (dialect = Dialect .POSTGRES , flatten = True )
312- data = json .dumps ([
313- {"id" : 1 , "meta" : {"city" : "NYC" }},
314- {"id" : 2 , "meta" : {"city" : "LA" , "state" : "CA" }},
315- ])
336+ data = json .dumps (
337+ [
338+ {"id" : 1 , "meta" : {"city" : "NYC" }},
339+ {"id" : 2 , "meta" : {"city" : "LA" , "state" : "CA" }},
340+ ]
341+ )
316342 result = conv .convert (data , table_name = "users" )
317343 # The INSERT should have matching column/value counts per row
318344 for block in result .split ("\n \n " ):
319- if ' INSERT INTO' in block and '"users"' in block :
320- cols = re .search (r' \(([^)]+)\)\s*VALUES' , block )
321- vals = re .search (r' VALUES\s*(.+)' , block , re .DOTALL )
345+ if " INSERT INTO" in block and '"users"' in block :
346+ cols = re .search (r" \(([^)]+)\)\s*VALUES" , block )
347+ vals = re .search (r" VALUES\s*(.+)" , block , re .DOTALL )
322348 if cols and vals :
323349 c_count = len ([c for c in cols .group (1 ).split ("," ) if c .strip ()])
324350 # Count value groups (rows)
325- row_matches = re .findall (r' \(([^)]+)\)' , vals .group (1 ))
351+ row_matches = re .findall (r" \(([^)]+)\)" , vals .group (1 ))
326352 for row_str in row_matches :
327353 v_count = len ([v for v in row_str .split ("," ) if v .strip ()])
328354 assert v_count == c_count , (
@@ -334,10 +360,12 @@ def test_flatten_missing_sub_key_gets_null(self):
334360 def test_flatten_nested_dict_all_keys_present (self ):
335361 """When all objects have the same nested sub-keys, no NULL padding needed."""
336362 conv = JSONToSQLConverter (dialect = Dialect .POSTGRES , flatten = True )
337- data = json .dumps ([
338- {"id" : 1 , "meta" : {"city" : "NYC" , "state" : "NY" }},
339- {"id" : 2 , "meta" : {"city" : "LA" , "state" : "CA" }},
340- ])
363+ data = json .dumps (
364+ [
365+ {"id" : 1 , "meta" : {"city" : "NYC" , "state" : "NY" }},
366+ {"id" : 2 , "meta" : {"city" : "LA" , "state" : "CA" }},
367+ ]
368+ )
341369 result = conv .convert (data , table_name = "users" )
342370 assert '"meta_city"' in result
343371 assert '"meta_state"' in result
@@ -352,13 +380,15 @@ def test_nested_object_key_not_shadowed_by_fk(self):
352380 """A nested object's own key that matches the parent FK column name
353381 should NOT be silently replaced by the parent's FK value."""
354382 conv = JSONToSQLConverter (dialect = Dialect .POSTGRES , flatten = True )
355- data = json .dumps ({
356- "id" : 1 ,
357- "name" : "Alice" ,
358- "orders" : [
359- {"users_id" : 999 , "product" : "Widget" , "qty" : 3 },
360- ],
361- })
383+ data = json .dumps (
384+ {
385+ "id" : 1 ,
386+ "name" : "Alice" ,
387+ "orders" : [
388+ {"users_id" : 999 , "product" : "Widget" , "qty" : 3 },
389+ ],
390+ }
391+ )
362392 result = conv .convert (data , table_name = "users" )
363393 # The child table should preserve the nested object's users_id value (999)
364394 # rather than silently replacing it with the parent's id (1)
@@ -384,10 +414,12 @@ def test_mixed_types_in_column(self, converter_postgres):
384414
385415 def test_missing_keys_across_rows (self , converter_postgres ):
386416 # Objects with different keys should all get columns
387- data = json .dumps ([
388- {"name" : "Alice" , "age" : 30 },
389- {"name" : "Bob" , "email" : "bob@test.com" },
390- ])
417+ data = json .dumps (
418+ [
419+ {"name" : "Alice" , "age" : 30 },
420+ {"name" : "Bob" , "email" : "bob@test.com" },
421+ ]
422+ )
391423 result = converter_postgres .convert (data , table_name = "users" )
392424 assert "email" in result
393425 assert "age" in result
@@ -427,14 +459,16 @@ def test_generate_schema_single_object(self, converter_postgres):
427459 def test_generate_schema_with_flatten_nested_array (self ):
428460 """generate_schema with flatten=True should produce CREATE TABLE for nested arrays."""
429461 conv = JSONToSQLConverter (dialect = Dialect .POSTGRES , flatten = True )
430- data = json .dumps ({
431- "id" : 1 ,
432- "name" : "Alice" ,
433- "orders" : [
434- {"product" : "Widget" , "qty" : 3 },
435- {"product" : "Gadget" , "qty" : 1 },
436- ],
437- })
462+ data = json .dumps (
463+ {
464+ "id" : 1 ,
465+ "name" : "Alice" ,
466+ "orders" : [
467+ {"product" : "Widget" , "qty" : 3 },
468+ {"product" : "Gadget" , "qty" : 1 },
469+ ],
470+ }
471+ )
438472 result = conv .generate_schema (data , table_name = "users" )
439473 assert "CREATE TABLE" in result
440474 # Should include the nested table schema
@@ -476,6 +510,7 @@ def test_version_in_init_matches_pyproject(self):
476510 import tomli as tomllib # Python < 3.11 backport
477511
478512 from json2sql import __version__
513+
479514 pyproject = Path (__file__ ).resolve ().parent .parent / "pyproject.toml"
480515 with open (pyproject , "rb" ) as f :
481516 data = tomllib .load (f )
0 commit comments