---
title: 'Subforms'
description: "Fitur subform menyediakan instance JSONEval yang terisolasi penuh untuk kolom array yang memiliki properti items. Ini memungkinkan elemen array yang kompleks untuk dievaluasi, divalidasi, dan dikelola secara mandiri dari schema induknya."
navigation:
  icon: 'i-ph-book-open'
seo:
  title: 'Subforms'
  description: "Fitur subform menyediakan instance JSONEval yang terisolasi penuh untuk kolom array yang memiliki properti items. Ini memungkinkan elemen array yang kompleks untuk dievaluasi, divalidasi, dan dikelola secara mandiri dari schema induknya."
---

## Gambaran Umum


## Konsep Utama

### Apa itu Subform?

Subform akan dibuat secara otomatis saat sebuah kolom di dalam schema memiliki:
- `type: "array"`
- Properti `items` yang berisi struktur untuk setiap elemen array

### Isolasi

Setiap subform adalah instance JSONEval yang **benar-benar terisolasi** dengan:
- Schema sendiri (dibuat dari isi `items`)
- Salinan `$params` dari induk untuk konstanta/referensi bersama
- Status evaluasi yang mandiri
- Tidak mengubah data induk secara langsung

## Struktur Schema

### Contoh Schema Induk

```json
{
  "$params": {
    "constants": {
      "MAX_RIDERS": 5
    }
  },
  "riders": {
    "type": "array",
    "title": "Rider",
    "items": {
      "$layout": {
        "type": "VerticalLayout",
        "elements": [
          { "$ref": "#/riders/properties/name" },
          { "$ref": "#/riders/properties/premium" }
        ]
      },
      "properties": {
        "name": {
          "type": "string",
          "title": "Nama Rider",
          "rules": {
            "required": { "value": true }
          }
        },
        "premium": {
          "type": "number",
          "title": "Jumlah Premi"
        }
      }
    }
  }
}
```

### Schema Subform yang Dihasilkan

```json
{
  "$params": {
    "constants": {
      "MAX_RIDERS": 5
    }
  },
  "riders": {
    "type": "object",
    "$layout": {
      "type": "VerticalLayout",
      "elements": [
        { "$ref": "#/riders/properties/name" },
        { "$ref": "#/riders/properties/premium" }
      ]
    },
    "properties": {
      "name": {
        "type": "string",
        "title": "Nama Rider",
        "rules": {
          "required": { "value": true }
        }
      },
      "premium": {
        "type": "number",
        "title": "Jumlah Premi"
      }
    }
  }
}
```

## Metode API

### 1. `evaluate_subform`

Mengevaluasi subform dengan data dan jalur selektif opsional.

```rust
pub fn evaluate_subform(
    &mut self,
    subform_path: &str,
    data: &str,
    context: Option<&str>,
    paths: Option<&[String]>,
) -> Result<(), String>
```

**Parameter:**
- **`subform_path`**: Jalur schema yang menunjuk ke array yang berisi subform (misal: `#/riders`).
- **`data`**: Data JSON dalam bentuk string untuk item array spesifik yang sedang dievaluasi.
- **`context`**: Data konteks JSON opsional dalam bentuk string.
- **`paths`**: Daftar jalur opsional untuk [Evaluasi Selektif](optimize-selective-evaluation) di dalam item array.

**Contoh:**
::code-group
  ```rust [Rust]
  // Evaluasi penuh
  eval.evaluate_subform("#/riders", &data_str, None, None)?;

  // Evaluasi selektif (hanya kolom tertentu)
  let paths = vec!["name".to_string()];
  eval.evaluate_subform("#/riders", &data_str, None, Some(&paths))?;
  ```
  ```csharp [C#]
  // Evaluasi penuh
  eval.EvaluateSubform("#/riders", dataStr);

  // Evaluasi selektif
  var paths = new[] { "name" };
  eval.EvaluateSubform("#/riders", dataStr, paths: paths);
  ```
  ```typescript [React Native]
  // Evaluasi penuh
  await eval.evaluateSubform({ subformPath: "#/riders", data: dataStr });

  // Evaluasi selektif (hanya kolom tertentu)
  await eval.evaluateSubform({ subformPath: "#/riders", data: dataStr, paths: ["name"] });
  ```
  ```typescript [Web / NodeJS]
  // Evaluasi penuh
  await eval.evaluateSubform({ subformPath: "#/riders", data: dataStr });

  // Evaluasi selektif (hanya kolom tertentu)
  await eval.evaluateSubform({ subformPath: "#/riders", data: dataStr, paths: ["name"] });
  ```
::

### 2. `validate_subform`

Memvalidasi data subform terhadap aturan schemanya.

```rust
pub fn validate_subform(
    &mut self,
    subform_path: &str,
    data: &str,
    context: Option<&str>,
    paths: Option<&[String]>,
) -> Result<ValidationResult, String>
```

**Parameter:**
- **`subform_path`**: Jalur schema yang menunjuk ke subform.
- **`data`**: Data JSON dalam bentuk string untuk divalidasi terhadap schema yang dihasilkan subform.
- **`context`**: Data konteks JSON opsional dalam bentuk string.
- **`paths`**: Jalur kolom spesifik opsional untuk divalidasi, melewati yang lainnya.

**Contoh:**
::code-group
  ```rust [Rust]
  let data = json!({ "riders": { "name": "" } }); // Premi hilang
  let data_str = serde_json::to_string(&data).unwrap();
  let result = eval.validate_subform("#/riders", &data_str, None, None)?;

  if result.has_error {
      for (field, error) in result.errors {
          println!("Field: {}, Rule: {}, Message: {}", 
                   field, error.rule_type, error.message);
      }
  }
  ```
  ```csharp [C#]
  var dataStr = "{\"riders\": {\"name\": \"\"}}"; // Premi hilang
  var result = eval.ValidateSubform("#/riders", dataStr);

  if (result.HasError) {
      foreach (var kvp in result.Errors) {
          Console.WriteLine($"Field: {kvp.Key}, Rule: {kvp.Value.RuleType}, Message: {kvp.Value.Message}");
      }
  }
  ```
  ```typescript [React Native]
  const dataStr = JSON.stringify({ riders: { name: "" } }); // Premi hilang
  const result = await eval.validate_subform({ subformPath: "#/riders", data: dataStr });

  if (result.has_error) {
      Object.entries(result.errors).forEach(([field, error]) => {
          console.log(`Field: ${field}, Rule: ${error.rule_type}, Message: ${error.message}`);
      });
  }
  ```
  ```typescript [Web / NodeJS]
  const dataStr = JSON.stringify({ riders: { name: "" } }); // Premi hilang
  const result = await eval.validate_subform({ subformPath: "#/riders", data: dataStr });

  if (result.has_error) {
      Object.entries(result.errors).forEach(([field, error]) => {
          console.log(`Field: ${field}, Rule: ${error.rule_type}, Message: ${error.message}`);
      });
  }
  ```
::

### 3. `evaluate_dependents_subform`

Mengevaluasi kolom yang bergantung (dependent) saat kolom tertentu di subform berubah.

```rust
pub fn evaluate_dependents_subform(
    &mut self,
    subform_path: &str,
    changed_paths: &[String],
    data: Option<&str>,
    context: Option<&str>,
    re_evaluate: bool,
) -> Result<Value, String>
```

**Parameter:**
- **`subform_path`**: Jalur schema yang menunjuk ke subform.
- **`changed_paths`**: Array kolom di dalam subform ini yang telah dimodifikasi secara terlihat.
- **`data`**: Data saat ini dari item spesifik tersebut.
- **`context`**: Data konteks opsional.
- **`re_evaluate`**: Apakah akan menjalankan pass evaluasi secara otomatis pada dependen yang diselesaikan sebelum dikembalikan.

**Contoh:**
::code-group
  ```rust [Rust]
  // Saat riders.premium berubah, kalkulasi ulang kolom dependen
  let changed = vec!["#/riders/properties/premium".to_string()];
  eval.evaluate_dependents_subform(
      "#/riders",
      &changed,
      Some(&data_str),
      None,
      true // picu evaluasi ulang
  )?;
  ```
  ```csharp [C#]
  var changed = new[] { "#/riders/properties/premium" };
  eval.EvaluateDependentsSubform(
      "#/riders",
      changedPaths: changed,
      data: dataStr,
      reEvaluate: true
  );
  ```
  ```typescript [React Native]
  const changed = ["#/riders/properties/premium"];
  await eval.evaluateDependentsSubform({
      subformPath: "#/riders",
      changedPaths: changed,
      data: dataStr,
      reEvaluate: true
  });
  ```
  ```typescript [Web / NodeJS]
  const changed = ["#/riders/properties/premium"];
  await eval.evaluateDependentsSubform({
      subformPath: "#/riders",
      changedPaths: changed,
      data: dataStr,
      reEvaluate: true
  });
  ```
::

### 4. `resolve_layout_subform`

Menyelesaikan referensi layout (misal: `$ref`) di dalam layout subform.

```rust
pub fn resolve_layout_subform(
    &mut self,
    subform_path: &str,
    evaluate: bool,
) -> Result<(), String>
```

**Contoh:**
::code-group
  ```rust [Rust]
  // Selesaikan layout tanpa evaluasi
  eval.resolve_layout_subform("#/riders", false)?;

  // Selesaikan layout dengan evaluasi
  eval.resolve_layout_subform("#/riders", true)?;
  ```
  ```csharp [C#]
  // Selesaikan layout tanpa evaluasi
  eval.ResolveLayoutSubform("#/riders", false);

  // Selesaikan layout dengan evaluasi
  eval.ResolveLayoutSubform("#/riders", true);
  ```
  ```typescript [React Native]
  // Selesaikan layout tanpa evaluasi
  await eval.resolveLayoutSubform({ subformPath: "#/riders", evaluate: false });

  // Selesaikan layout dengan evaluasi
  await eval.resolveLayoutSubform({ subformPath: "#/riders", evaluate: true });
  ```
  ```typescript [Web / NodeJS]
  // Selesaikan layout tanpa evaluasi
  await eval.resolveLayoutSubform({ subformPath: "#/riders", evaluate: false });

  // Selesaikan layout dengan evaluasi
  await eval.resolveLayoutSubform({ subformPath: "#/riders", evaluate: true });
  ```
::

### 5. `get_evaluated_schema_subform`

Mengambil schema hasil evaluasi dari subform.

```rust
pub fn get_evaluated_schema_subform(
    &mut self,
    subform_path: &str,
    resolve_layout: bool,
) -> Value
```

**Contoh:**
::code-group
  ```rust [Rust]
  let schema = eval.get_evaluated_schema_subform("#/riders", true);
  println!("{}", serde_json::to_string_pretty(&schema).unwrap());
  ```
  ```csharp [C#]
  var schema = eval.GetEvaluatedSchemaSubform("#/riders", true);
  Console.WriteLine(schema); // string JSON
  ```
  ```typescript [React Native]
  const schema = await eval.getEvaluatedSchemaSubform({ subformPath: "#/riders", resolveLayout: true });
  console.log(JSON.stringify(schema, null, 2));
  ```
  ```typescript [Web / NodeJS]
  const schema = await eval.getEvaluatedSchemaSubform({ subformPath: "#/riders", resolveLayout: true });
  console.log(JSON.stringify(schema, null, 2));
  ```
::

### 6. `get_schema_value_subform`

Mengambil semua isi kolom `.value` dari schema subform.

```rust
pub fn get_schema_value_subform(
    &mut self,
    subform_path: &str,
) -> Value
```

**Contoh:**
::code-group
  ```rust [Rust]
  let values = eval.get_schema_value_subform("#/riders");
  ```
  ```csharp [C#]
  var values = eval.GetSchemaValueSubform("#/riders");
  ```
  ```typescript [React Native]
  const values = await eval.getSchemaValueSubform({ subformPath: "#/riders" });
  ```
  ```typescript [Web / NodeJS]
  const values = await eval.getSchemaValueSubform({ subformPath: "#/riders" });
  ```
::

### 7. `get_evaluated_schema_without_params_subform`

Mengambil schema hasil evaluasi tanpa `$params`.

```rust
pub fn get_evaluated_schema_without_params_subform(
    &mut self,
    subform_path: &str,
    resolve_layout: bool,
) -> Value
```

**Contoh:**
::code-group
  ```rust [Rust]
  let schema = eval.get_evaluated_schema_without_params_subform("#/riders", false);
  ```
  ```csharp [C#]
  var schema = eval.GetEvaluatedSchemaWithoutParamsSubform("#/riders", false);
  ```
  ```typescript [React Native]
  const schema = await eval.getEvaluatedSchemaWithoutParamsSubform({ subformPath: "#/riders", resolveLayout: false });
  ```
  ```typescript [Web / NodeJS]
  const schema = await eval.getEvaluatedSchemaWithoutParamsSubform({ subformPath: "#/riders", resolveLayout: false });
  ```
::

### 8. `get_evaluated_schema_by_path_subform`

Mengambil schema hasil evaluasi pada jalur tertentu di dalam subform.

```rust
pub fn get_evaluated_schema_by_path_subform(
    &mut self,
    subform_path: &str,
    schema_path: &str,
    skip_layout: bool,
) -> Option<Value>
```

**Contoh:**
::code-group
  ```rust [Rust]
  let name_schema = eval.get_evaluated_schema_by_path_subform(
      "#/riders",
      "#/riders/properties/name",
      false
  );
  ```
  ```csharp [C#]
  var nameSchema = eval.GetEvaluatedSchemaByPathSubform(
      "#/riders",
      "#/riders/properties/name",
      false
  );
  ```
  ```typescript [React Native]
  const nameSchema = await eval.getEvaluatedSchemaByPathSubform({
      subformPath: "#/riders",
      path: "#/riders/properties/name",
      skipLayout: false
  });
  ```
  ```typescript [Web / NodeJS]
  const nameSchema = await eval.getEvaluatedSchemaByPathSubform({
      subformPath: "#/riders",
      path: "#/riders/properties/name",
      skipLayout: false
  });
  ```
::

### Fungsi Pembantu (Helper Methods)

#### `get_subform_paths`

Mengambil daftar semua jalur subform yang tersedia.

```rust
pub fn get_subform_paths(&self) -> Vec<String>
```

**Contoh:**
::code-group
  ```rust [Rust]
  let paths = eval.get_subform_paths();
  for path in paths {
      println!("Subform tersedia di: {}", path);
  }
  ```
  ```csharp [C#]
  var paths = eval.GetSubformPaths();
  foreach (var path in paths) {
      Console.WriteLine($"Subform tersedia di: {path}");
  }
  ```
  ```typescript [React Native]
  const paths = await eval.getSubformPaths();
  for (const path of paths) {
      console.log(`Subform tersedia di: ${path}`);
  }
  ```
  ```typescript [Web / NodeJS]
  const paths = await eval.getSubformPaths();
  for (const path of paths) {
      console.log(`Subform tersedia di: ${path}`);
  }
  ```
::

#### `has_subform`

Memeriksa apakah subform ada pada jalur yang diberikan.

```rust
pub fn has_subform(&self, subform_path: &str) -> bool
```

**Contoh:**
::code-group
  ```rust [Rust]
  if eval.has_subform("#/riders") {
      // Bekerja dengan subform riders
  }
  ```
  ```csharp [C#]
  if (eval.HasSubform("#/riders")) {
      // Bekerja dengan subform riders
  }
  ```
  ```typescript [React Native]
  if (await eval.hasSubform("#/riders")) {
      // Bekerja dengan subform riders
  }
  ```
  ```typescript [Web / NodeJS]
  if (await eval.hasSubform("#/riders")) {
      // Bekerja dengan subform riders
  }
  ```
::

## Alur Penggunaan (Usage Flow)

### Alur Kerja Umum

1. **Buat instance induk JSONEval** - Subform secara otomatis terdeteksi dan dibuat
2. **Periksa subform yang tersedia** - Gunakan `get_subform_paths()` atau `has_subform()`
3. **Evaluasi subform** - Panggil `evaluate_subform()` dengan data elemen array
4. **Validasi data** - Gunakan `validate_subform()` untuk memeriksa aturan
5. **Tangani perubahan** - Gunakan `evaluate_dependents_subform()` untuk pembaruan reaktif
6. **Ambil hasil** - Gunakan `get_evaluated_schema_subform()` untuk mengambil nilai yang dihitung

### Contoh: Alur Lengkap

::code-group
  ```rust [Rust]
  use json_eval_rs::JSONEval;
  use serde_json::json;

  // 1. Buat schema induk dengan kolom array
  let schema = json!({
      "$params": {
          "constants": { "TAX_RATE": 0.1 }
      },
      "line_items": {
          "type": "array",
          "items": {
              "properties": {
                  "price": { "type": "number" },
                  "tax": {
                      "type": "number",
                      "$evaluation": {
                          "*": [
                              { "$ref": "#/line_items/properties/price" },
                              { "$ref": "#/$params/constants/TAX_RATE" }
                          ]
                      }
                  }
              }
          }
      }
  });

  let schema_str = serde_json::to_string(&schema).unwrap();
  let mut eval = JSONEval::new(&schema_str, None, None).unwrap();

  // 2. Periksa subform ada
  assert!(eval.has_subform("#/line_items"));

  // 3. Evaluasi subform dengan data
  let item_data = json!({
      "line_items": { "price": 100.0 }
  });
  let item_data_str = serde_json::to_string(&item_data).unwrap();

  eval.evaluate_subform("#/line_items", &item_data_str, None, None).unwrap();

  // 4. Ambil schema hasil evaluasi dengan pajak yang dihitung
  let result = eval.get_evaluated_schema_subform("#/line_items", false);
  println!("Result: {}", serde_json::to_string_pretty(&result).unwrap());
  ```
  ```csharp [C#]
  using JsonEvalRs;

  var schemaStr = @"{
      ""$params"": {
          ""constants"": { ""TAX_RATE"": 0.1 }
      },
      ""line_items"": {
          ""type"": ""array"",
          ""items"": {
              ""properties"": {
                  ""price"": { ""type"": ""number"" },
                  ""tax"": {
                      ""type"": ""number"",
                      ""$evaluation"": {
                          ""*"": [
                              { ""$ref"": ""#/line_items/properties/price"" },
                              { ""$ref"": ""#/$params/constants/TAX_RATE"" }
                          ]
                      }
                  }
              }
          }
      }
  }";

  using var eval = new JSONEval(schemaStr);

  // 2. Periksa subform ada
  if (eval.HasSubform("#/line_items")) {
      // 3. Evaluasi subform dengan data
      var itemDataStr = "{\"line_items\": {\"price\": 100.0}}";
      eval.EvaluateSubform("#/line_items", itemDataStr);

      // 4. Ambil schema hasil evaluasi dengan pajak yang dihitung
      var result = eval.GetEvaluatedSchemaSubform("#/line_items", false);
      Console.WriteLine($"Result: {result}");
  }
  ```
  ```typescript [React Native]
  import { JSONEval } from "@json-eval-rs/react-native";

  const schema = {
      $params: {
          constants: { TAX_RATE: 0.1 }
      },
      line_items: {
          type: "array",
          items: {
              properties: {
                  price: { type: "number" },
                  tax: {
                      type: "number",
                      $evaluation: {
                          "*": [
                              { $ref: "#/line_items/properties/price" },
                              { $ref: "#/$params/constants/TAX_RATE" }
                          ]
                      }
                  }
              }
          }
      }
  };

  const eval = new JSONEval({ schema: JSON.stringify(schema) });

  // 2. Periksa subform ada
  if (await eval.hasSubform("#/line_items")) {
      // 3. Evaluasi subform dengan data
      const itemDataStr = JSON.stringify({ line_items: { price: 100.0 } });
      await eval.evaluateSubform({ subformPath: "#/line_items", data: itemDataStr });

      // 4. Ambil schema hasil evaluasi dengan pajak yang dihitung
      const result = await eval.getEvaluatedSchemaSubform({ subformPath: "#/line_items", resolveLayout: false });
      console.log(`Result: ${JSON.stringify(result, null, 2)}`);
  }
  ```
  ```typescript [Web / NodeJS]
  import { JSONEval } from "@json-eval-rs/node"; // atau /bundler, /vanilla

  const schema = {
      $params: {
          constants: { TAX_RATE: 0.1 }
      },
      line_items: {
          type: "array",
          items: {
              properties: {
                  price: { type: "number" },
                  tax: {
                      type: "number",
                      $evaluation: {
                          "*": [
                              { $ref: "#/line_items/properties/price" },
                              { $ref: "#/$params/constants/TAX_RATE" }
                          ]
                      }
                  }
              }
          }
      }
  };

  const eval = new JSONEval({ schema: JSON.stringify(schema) });

  // 2. Periksa subform ada
  if (await eval.hasSubform("#/line_items")) {
      // 3. Evaluasi subform dengan data
      const itemDataStr = JSON.stringify({ line_items: { price: 100.0 } });
      await eval.evaluateSubform({ subformPath: "#/line_items", data: itemDataStr });

      // 4. Ambil schema hasil evaluasi dengan pajak yang dihitung
      const result = await eval.getEvaluatedSchemaSubform({ subformPath: "#/line_items", resolveLayout: false });
      console.log(`Result: ${JSON.stringify(result, null, 2)}`);
  }
  ```
::

## Keuntungan

### 1. **Isolasi Penuh**
- Subform tidak dapat secara tidak sengaja mengubah data induk
- Setiap subform memiliki konteks evaluasi sendiri
- Batasan yang jelas antara schema induk dan anak

### 2. **Performa**
- Subform dibuat satu kali saat pembacaan (parse) schema
- Tidak ada overhead runtime untuk deteksi
- Penggunaan memori yang efisien dengan boxed instances

### 3. **Fleksibilitas**
- Bekerja dengan elemen array seolah-olah sebagai form yang berdiri sendiri
- Memvalidasi item satu per satu sebelum ditambahkan ke array
- Mendukung struktur nested yang kompleks

### 4. **Konsistensi**
- API yang sama dengan induk JSONEval
- Semua fitur berjalan di subform (evaluasi, validasi, dependen, layout)
- Perilaku yang dapat diprediksi

## Detail Implementasi

### Deteksi Saat Parse (Parse Time Detection)

Subform dideteksi selama pembacaan schema dalam satu pass:

```rust
// Dalam parse_schema.rs
fn collect_subform_fields(value: &Value, path: &str, ...) {
    if type == "array" && has items {
        create_subform(path, field_map, items, ...);
        return; // Jangan rekursi ke dalam items
    }
    // Rekursi ke anak lainnya
}
```

### Konstruksi Schema

Setiap schema subform dibangun sebagai:

```rust
{
    "$params": <disalin dari induk>,
    "<field_name>": {
        "type": "object",
        ...items content...
    }
}
```

### Penyimpanan

Subform disimpan di dalam induk JSONEval:

```rust
pub struct JSONEval {
    ...
    pub subforms: IndexMap<String, Box<JSONEval>>,
    ...
}
```

## Batasan (Limitations)

1. **Tidak Ada Mutasi Data Langsung** - Subform bekerja dengan data terisolasi, perubahan tidak secara otomatis menyebar ke induk
2. **Format Jalur (Path Format)** - Jalur subform menggunakan format schema (`#/kolom`) bukan format JSON pointer
3. **Satu Tingkat (Single Level)** - Subform bertingkat (array di dalam array) masing-masing akan mendapatkan instance terisolasi sendiri
