RoyAalekh commited on
Commit
0a6dddb
·
1 Parent(s): 264ecd0

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

Files changed (2) hide show
  1. app.py +24 -3
  2. 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: this.getSelectedValues('utilityOptions'),
352
- phenology_stages: this.getSelectedValues('phenologyOptions'),
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',