Spaces:
Running
Running
Fix JSON validation issue for array fields
Browse files- Added 'mode=before' to field validators for utility, phenology_stages, and photographs
- Validators now handle JSON string input and parse to arrays/objects before validation
- This fixes the validation error where arrays were received as JSON strings
- Frontend sends proper JavaScript arrays which get serialized to JSON strings in transit
- Backend now properly deserializes these strings back to arrays for validation
- app.py +24 -3
- static/app.js +10 -2
app.py
CHANGED
@@ -303,9 +303,16 @@ class TreeCreate(BaseModel):
|
|
303 |
# 12. Notes
|
304 |
notes: Optional[str] = Field(None, max_length=2000, description="Additional observations")
|
305 |
|
306 |
-
@field_validator("utility")
|
307 |
@classmethod
|
308 |
def validate_utility(cls, v):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
309 |
if v is not None:
|
310 |
valid_utilities = [
|
311 |
"Religious", "Timber", "Biodiversity", "Hydrological benefit",
|
@@ -316,9 +323,16 @@ class TreeCreate(BaseModel):
|
|
316 |
raise ValueError(f"Invalid utility: {item}. Must be one of: {valid_utilities}")
|
317 |
return v
|
318 |
|
319 |
-
@field_validator("phenology_stages")
|
320 |
@classmethod
|
321 |
def validate_phenology(cls, v):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
322 |
if v is not None:
|
323 |
valid_stages = [
|
324 |
"New leaves", "Old leaves", "Open flowers", "Fruiting",
|
@@ -329,9 +343,16 @@ class TreeCreate(BaseModel):
|
|
329 |
raise ValueError(f"Invalid phenology stage: {stage}. Must be one of: {valid_stages}")
|
330 |
return v
|
331 |
|
332 |
-
@field_validator("photographs")
|
333 |
@classmethod
|
334 |
def validate_photographs(cls, v):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
335 |
if v is not None:
|
336 |
valid_categories = ["Leaf", "Bark", "Fruit", "Seed", "Flower", "Full tree"]
|
337 |
for category in v.keys():
|
|
|
303 |
# 12. Notes
|
304 |
notes: Optional[str] = Field(None, max_length=2000, description="Additional observations")
|
305 |
|
306 |
+
@field_validator("utility", mode='before')
|
307 |
@classmethod
|
308 |
def validate_utility(cls, v):
|
309 |
+
# Handle JSON string input
|
310 |
+
if isinstance(v, str):
|
311 |
+
try:
|
312 |
+
v = json.loads(v)
|
313 |
+
except json.JSONDecodeError:
|
314 |
+
raise ValueError(f"Invalid JSON string for utility: {v}")
|
315 |
+
|
316 |
if v is not None:
|
317 |
valid_utilities = [
|
318 |
"Religious", "Timber", "Biodiversity", "Hydrological benefit",
|
|
|
323 |
raise ValueError(f"Invalid utility: {item}. Must be one of: {valid_utilities}")
|
324 |
return v
|
325 |
|
326 |
+
@field_validator("phenology_stages", mode='before')
|
327 |
@classmethod
|
328 |
def validate_phenology(cls, v):
|
329 |
+
# Handle JSON string input
|
330 |
+
if isinstance(v, str):
|
331 |
+
try:
|
332 |
+
v = json.loads(v)
|
333 |
+
except json.JSONDecodeError:
|
334 |
+
raise ValueError(f"Invalid JSON string for phenology_stages: {v}")
|
335 |
+
|
336 |
if v is not None:
|
337 |
valid_stages = [
|
338 |
"New leaves", "Old leaves", "Open flowers", "Fruiting",
|
|
|
343 |
raise ValueError(f"Invalid phenology stage: {stage}. Must be one of: {valid_stages}")
|
344 |
return v
|
345 |
|
346 |
+
@field_validator("photographs", mode='before')
|
347 |
@classmethod
|
348 |
def validate_photographs(cls, v):
|
349 |
+
# Handle JSON string input
|
350 |
+
if isinstance(v, str):
|
351 |
+
try:
|
352 |
+
v = json.loads(v)
|
353 |
+
except json.JSONDecodeError:
|
354 |
+
raise ValueError(f"Invalid JSON string for photographs: {v}")
|
355 |
+
|
356 |
if v is not None:
|
357 |
valid_categories = ["Leaf", "Bark", "Fruit", "Seed", "Flower", "Full tree"]
|
358 |
for category in v.keys():
|
static/app.js
CHANGED
@@ -339,6 +339,9 @@ class TreeTrackApp {
|
|
339 |
async handleSubmit(e) {
|
340 |
e.preventDefault();
|
341 |
|
|
|
|
|
|
|
342 |
const treeData = {
|
343 |
latitude: parseFloat(document.getElementById('latitude').value),
|
344 |
longitude: parseFloat(document.getElementById('longitude').value),
|
@@ -348,14 +351,19 @@ class TreeTrackApp {
|
|
348 |
tree_code: document.getElementById('treeCode').value || null,
|
349 |
height: document.getElementById('height').value ? parseFloat(document.getElementById('height').value) : null,
|
350 |
width: document.getElementById('width').value ? parseFloat(document.getElementById('width').value) : null,
|
351 |
-
utility:
|
352 |
-
phenology_stages:
|
353 |
storytelling_text: document.getElementById('storytellingText').value || null,
|
354 |
storytelling_audio: this.audioFile,
|
355 |
photographs: Object.keys(this.uploadedPhotos).length > 0 ? this.uploadedPhotos : null,
|
356 |
notes: document.getElementById('notes').value || null
|
357 |
};
|
358 |
|
|
|
|
|
|
|
|
|
|
|
359 |
try {
|
360 |
const response = await fetch('/api/trees', {
|
361 |
method: 'POST',
|
|
|
339 |
async handleSubmit(e) {
|
340 |
e.preventDefault();
|
341 |
|
342 |
+
const utilityValues = this.getSelectedValues('utilityOptions');
|
343 |
+
const phenologyValues = this.getSelectedValues('phenologyOptions');
|
344 |
+
|
345 |
const treeData = {
|
346 |
latitude: parseFloat(document.getElementById('latitude').value),
|
347 |
longitude: parseFloat(document.getElementById('longitude').value),
|
|
|
351 |
tree_code: document.getElementById('treeCode').value || null,
|
352 |
height: document.getElementById('height').value ? parseFloat(document.getElementById('height').value) : null,
|
353 |
width: document.getElementById('width').value ? parseFloat(document.getElementById('width').value) : null,
|
354 |
+
utility: utilityValues.length > 0 ? utilityValues : [],
|
355 |
+
phenology_stages: phenologyValues.length > 0 ? phenologyValues : [],
|
356 |
storytelling_text: document.getElementById('storytellingText').value || null,
|
357 |
storytelling_audio: this.audioFile,
|
358 |
photographs: Object.keys(this.uploadedPhotos).length > 0 ? this.uploadedPhotos : null,
|
359 |
notes: document.getElementById('notes').value || null
|
360 |
};
|
361 |
|
362 |
+
// Debug log to check the data structure
|
363 |
+
console.log('Tree data being sent:', treeData);
|
364 |
+
console.log('Utility type:', typeof treeData.utility, treeData.utility);
|
365 |
+
console.log('Phenology type:', typeof treeData.phenology_stages, treeData.phenology_stages);
|
366 |
+
|
367 |
try {
|
368 |
const response = await fetch('/api/trees', {
|
369 |
method: 'POST',
|