Skip to content
Open
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
0a48f7a
Refactor object inference in JSON converter
Fellmonkey Aug 2, 2025
b44f0b1
Refactor model deserialization logic in C# template
Fellmonkey Aug 2, 2025
4c6b9f7
Handle optional properties in model From method
Fellmonkey Aug 9, 2025
b071cbc
synchronization with the Unity template
Fellmonkey Aug 14, 2025
e9586d2
Refactor model parsing for nullable and array properties
Fellmonkey Sep 25, 2025
5ad9a4b
Skip null parameters in request parameter loop
Fellmonkey Sep 25, 2025
953600d
Refactor model class name generation in template
Fellmonkey Sep 28, 2025
d9f6d71
Merge branch 'master' into improve-json-serialization
Fellmonkey Oct 1, 2025
cc2cd62
Add parse_value Twig function for DotNet models
Fellmonkey Oct 1, 2025
ebbad72
make generated array mappings null-safe
Fellmonkey Oct 1, 2025
ff2545a
lint
Fellmonkey Oct 3, 2025
faad585
Import Enums namespace conditionally in model template
Fellmonkey Oct 3, 2025
995ba87
Merge branch 'master' into improve-json-serialization
ChiragAgg5k Oct 4, 2025
10993e5
Refactor array handling in DotNet code generation
Fellmonkey Oct 13, 2025
76ce99c
Merge branch 'master' into improve-json-serialization
Fellmonkey Oct 13, 2025
c243bca
Merge remote-tracking branch 'upstream/master' into improve-json-seri…
Fellmonkey Jan 17, 2026
a91f388
Update docblock for getFunctions method
Fellmonkey Jan 17, 2026
2950f87
Refactor exception and extension handling
Fellmonkey Jan 19, 2026
488fe88
Refactor .NET model property serialization
Fellmonkey Feb 14, 2026
8fe9521
Always call ToList() for mapped arrays
Fellmonkey Feb 14, 2026
dfab06f
Merge branch 'master' into improve-json-serialization
Fellmonkey Feb 14, 2026
eab63b9
Add missing closing for toMapValue TwigFilter
Fellmonkey Feb 14, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 78 additions & 2 deletions src/SDK/Language/DotNet.php
Original file line number Diff line number Diff line change
Expand Up @@ -467,7 +467,7 @@ public function getFilters(): array
}

/**
* get sub_scheme and property_name functions
* get sub_scheme, property_name and parse_value functions
* @return TwigFunction[]
*/
public function getFunctions(): array
Expand All @@ -494,7 +494,7 @@ public function getFunctions(): array
}

return $result;
}),
}, ['is_safe' => ['html']]),
new TwigFunction('property_name', function (array $definition, array $property) {
$name = $property['name'];
$name = \str_replace('$', '', $name);
Expand All @@ -504,6 +504,82 @@ public function getFunctions(): array
}
return $name;
}),
new TwigFunction('parse_value', function (array $property, string $mapAccess, string $v) {
$required = $property['required'] ?? false;

// Handle sub_schema
if (isset($property['sub_schema']) && !empty($property['sub_schema'])) {
$subSchema = \ucfirst($property['sub_schema']);

if ($property['type'] === 'array') {
$arraySource = $required
? "((IEnumerable<object>){$mapAccess})"
: "({$v} as IEnumerable<object>)?";
return "{$arraySource}.Select(it => {$subSchema}.From(map: (Dictionary<string, object>)it)).ToList()";
} else {
if ($required) {
return "{$subSchema}.From(map: (Dictionary<string, object>){$mapAccess})";
}
return "({$v} as Dictionary<string, object>) is { } obj ? {$subSchema}.From(map: obj) : null";
}
}

// Handle enum
if (isset($property['enum']) && !empty($property['enum'])) {
$enumName = $property['enumName'] ?? $property['name'];
$enumClass = \ucfirst($enumName);

if ($required) {
return "new {$enumClass}({$mapAccess}.ToString())";
}
return "{$v} == null ? null : new {$enumClass}({$v}.ToString())";
}

// Handle arrays
if ($property['type'] === 'array') {
$itemsType = $property['items']['type'] ?? 'object';
$src = $required ? $mapAccess : $v;
$arraySource = $required
? "((IEnumerable<object>){$src})"
: "({$src} as IEnumerable<object>)?";

$selectExpression = match ($itemsType) {
'string' => 'x.ToString()',
'integer' => 'Convert.ToInt64(x)',
'number' => 'Convert.ToDouble(x)',
'boolean' => '(bool)x',
default => 'x'
};

return "{$arraySource}.Select(x => {$selectExpression}).ToList()";
}

// Handle integer/number
if ($property['type'] === 'integer' || $property['type'] === 'number') {
$convertMethod = $property['type'] === 'integer' ? 'Int64' : 'Double';

if ($required) {
return "Convert.To{$convertMethod}({$mapAccess})";
}
return "{$v} == null ? null : Convert.To{$convertMethod}({$v})";
}

// Handle boolean
if ($property['type'] === 'boolean') {
$typeName = $this->getTypeName($property);

if ($required) {
return "({$typeName}){$mapAccess}";
}
return "({$typeName}?){$v}";
}

// Handle string type
if ($required) {
return "{$mapAccess}.ToString()";
}
return "{$v}?.ToString()";
}, ['is_safe' => ['html']]),
];
}

Expand Down
1 change: 1 addition & 0 deletions templates/dotnet/Package/Client.cs.twig
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,7 @@ namespace {{ spec.title | caseUcfirst }}

foreach (var parameter in parameters)
{
if (parameter.Value == null) continue;
if (parameter.Key == "file")
{
var fileContent = parameters["file"] as MultipartFormDataContent;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,32 +7,60 @@ namespace {{ spec.title | caseUcfirst }}.Converters
{
public class ObjectToInferredTypesConverter : JsonConverter<object>
{
public override object Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
public override object? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
switch (reader.TokenType)
using (JsonDocument document = JsonDocument.ParseValue(ref reader))
{
case JsonTokenType.True:
return true;
case JsonTokenType.False:
return false;
case JsonTokenType.Number:
if (reader.TryGetInt64(out long l))
return ConvertElement(document.RootElement);
}
}

private object? ConvertElement(JsonElement element)
{
switch (element.ValueKind)
{
case JsonValueKind.Object:
var dictionary = new Dictionary<string, object?>();
foreach (var property in element.EnumerateObject())
{
return l;
dictionary[property.Name] = ConvertElement(property.Value);
}
return dictionary;

case JsonValueKind.Array:
var list = new List<object?>();
foreach (var item in element.EnumerateArray())
{
list.Add(ConvertElement(item));
}
return reader.GetDouble();
case JsonTokenType.String:
if (reader.TryGetDateTime(out DateTime datetime))
return list;

case JsonValueKind.String:
if (element.TryGetDateTime(out DateTime datetime))
{
return datetime;
}
return reader.GetString()!;
case JsonTokenType.StartObject:
return JsonSerializer.Deserialize<Dictionary<string, object>>(ref reader, options)!;
case JsonTokenType.StartArray:
return JsonSerializer.Deserialize<object[]>(ref reader, options)!;
return element.GetString();

case JsonValueKind.Number:
if (element.TryGetInt64(out long l))
{
return l;
}
return element.GetDouble();

case JsonValueKind.True:
return true;

case JsonValueKind.False:
return false;

case JsonValueKind.Null:
case JsonValueKind.Undefined:
return null;

default:
return JsonDocument.ParseValue(ref reader).RootElement.Clone();
throw new JsonException($"Unsupported JsonValueKind: {element.ValueKind}");
}
}

Expand Down
2 changes: 1 addition & 1 deletion templates/dotnet/Package/Exception.cs.twig
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,10 @@ namespace {{spec.title | caseUcfirst}}
this.Type = type;
this.Response = response;
}

public {{spec.title | caseUcfirst}}Exception(string message, Exception inner)
: base(message, inner)
{
}
}
}

2 changes: 1 addition & 1 deletion templates/dotnet/Package/Extensions/Extensions.cs.twig
Original file line number Diff line number Diff line change
Expand Up @@ -624,4 +624,4 @@ namespace {{ spec.title | caseUcfirst }}.Extensions
return GetMimeTypeFromExtension(System.IO.Path.GetExtension(path));
}
}
}
}
4 changes: 2 additions & 2 deletions templates/dotnet/Package/Models/InputFile.cs.twig
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
using System.IO;
using Appwrite.Extensions;
using {{ spec.title | caseUcfirst }}.Extensions;

namespace {{ spec.title | caseUcfirst }}.Models
{
Expand Down Expand Up @@ -38,4 +38,4 @@ namespace {{ spec.title | caseUcfirst }}.Models
SourceType = "bytes"
};
}
}
}
55 changes: 13 additions & 42 deletions templates/dotnet/Package/Models/Model.cs.twig
Original file line number Diff line number Diff line change
@@ -1,27 +1,29 @@

{% set DefinitionClass = definition.name | caseUcfirst | overrideIdentifier %}
using System;
using System.Linq;
using System.Collections.Generic;
using System.Text.Json;
using System.Text.Json.Serialization;
{% if definition.properties | filter(p => p.enum) | length > 0 %}
using {{ spec.title | caseUcfirst }}.Enums;
{% endif %}

namespace {{ spec.title | caseUcfirst }}.Models
{
public class {{ definition.name | caseUcfirst | overrideIdentifier }}
public class {{ DefinitionClass }}
{
{%~ for property in definition.properties %}
[JsonPropertyName("{{ property.name }}")]
public {{ sub_schema(property) | raw }} {{ property_name(definition, property) | overrideProperty(definition.name) }} { get; private set; }
public {{ sub_schema(property) }} {{ property_name(definition, property) | overrideProperty(definition.name) }} { get; private set; }

{%~ endfor %}
{%~ if definition.additionalProperties %}
public Dictionary<string, object> Data { get; private set; }

{%~ endif %}
public {{ definition.name | caseUcfirst | overrideIdentifier }}(
public {{ DefinitionClass }}(
{%~ for property in definition.properties %}
{{ sub_schema(property) | raw }} {{ property.name | caseCamel | escapeKeyword }}{% if not loop.last or (loop.last and definition.additionalProperties) %},{% endif %}
{{ sub_schema(property) }} {{ property.name | caseCamel | escapeKeyword }}{% if not loop.last or (loop.last and definition.additionalProperties) %},{% endif %}

{%~ endfor %}
{%~ if definition.additionalProperties %}
Expand All @@ -36,45 +38,14 @@ namespace {{ spec.title | caseUcfirst }}.Models
{%~ endif %}
}

public static {{ definition.name | caseUcfirst | overrideIdentifier }} From(Dictionary<string, object> map) => new {{ definition.name | caseUcfirst | overrideIdentifier }}(
public static {{ DefinitionClass }} From(Dictionary<string, object> map) => new {{ DefinitionClass }}(
{%~ for property in definition.properties %}
{%~ set v = 'v' ~ loop.index0 %}
{%~ set mapAccess = 'map["' ~ property.name ~ '"]' %}
{{ property.name | caseCamel | escapeKeyword | removeDollarSign }}:{{' '}}
{%- if property.sub_schema %}
{%- if property.type == 'array' -%}
map["{{ property.name }}"] is JsonElement jsonArray{{ loop.index }} ? jsonArray{{ loop.index }}.Deserialize<List<Dictionary<string, object>>>()!.Select(it => {{ property.sub_schema | caseUcfirst | overrideIdentifier }}.From(map: it)).ToList() : ((IEnumerable<Dictionary<string, object>>)map["{{ property.name }}"]).Select(it => {{ property.sub_schema | caseUcfirst | overrideIdentifier }}.From(map: it)).ToList()
{%- else -%}
{{ property.sub_schema | caseUcfirst | overrideIdentifier }}.From(map: map["{{ property.name }}"] is JsonElement jsonObj{{ loop.index }} ? jsonObj{{ loop.index }}.Deserialize<Dictionary<string, object>>()! : (Dictionary<string, object>)map["{{ property.name }}"])
{%- endif %}
{%- elseif property.enum %}
{%- set enumName = property['enumName'] ?? property.name -%}
{%- if not property.required -%}
map.TryGetValue("{{ property.name }}", out var enumRaw{{ loop.index }})
? enumRaw{{ loop.index }} == null
? null
: new {{ enumName | caseUcfirst }}(enumRaw{{ loop.index }}.ToString()!)
: null
{%- else -%}
new {{ enumName | caseUcfirst }}(map["{{ property.name }}"].ToString()!)
{%- endif %}
{%- else %}
{%- if property.type == 'array' -%}
map["{{ property.name }}"] is JsonElement jsonArrayProp{{ loop.index }} ? jsonArrayProp{{ loop.index }}.Deserialize<{{ property | typeName }}>()! : ({{ property | typeName }})map["{{ property.name }}"]
{%- else %}
{%- if property.type == "integer" or property.type == "number" %}
{%- if not property.required -%}map["{{ property.name }}"] == null ? null :{% endif %}Convert.To{% if property.type == "integer" %}Int64{% else %}Double{% endif %}(map["{{ property.name }}"])
{%- else %}
{%- if property.type == "boolean" -%}
({{ property | typeName }}{% if not property.required %}?{% endif %})map["{{ property.name }}"]
{%- else %}
{%- if not property.required -%}
map.TryGetValue("{{ property.name }}", out var {{ property.name | caseCamel | escapeKeyword | removeDollarSign }}) ? {{ property.name | caseCamel | escapeKeyword | removeDollarSign }}?.ToString() : null
{%- else -%}
map["{{ property.name }}"].ToString()
{%- endif %}
{%- endif %}
{%~ endif %}
{%~ endif %}
{%~ endif %}
{%- if not property.required -%}map.TryGetValue("{{ property.name }}", out var {{ v }}) ? {% endif -%}
{{ parse_value(property, mapAccess, v) }}
{%- if not property.required %} : null{% endif -%}
{%- if not loop.last or (loop.last and definition.additionalProperties) %},
{%~ endif %}
{%~ endfor %}
Expand Down
2 changes: 1 addition & 1 deletion templates/dotnet/Package/Models/UploadProgress.cs.twig
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,4 @@ namespace {{ spec.title | caseUcfirst }}
ChunksUploaded = chunksUploaded;
}
}
}
}
2 changes: 1 addition & 1 deletion templates/dotnet/Package/Query.cs.twig
Original file line number Diff line number Diff line change
Expand Up @@ -274,4 +274,4 @@ namespace {{ spec.title | caseUcfirst }}
return new Query("notTouches", attribute, new List<object> { values }).ToString();
}
}
}
}
4 changes: 2 additions & 2 deletions templates/dotnet/Package/Role.cs.twig
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
namespace Appwrite
namespace {{ spec.title | caseUcfirst }}
{
/// <summary>
/// Helper class to generate role strings for Permission.
Expand Down Expand Up @@ -89,4 +89,4 @@ namespace Appwrite
return $"label:{name}";
}
}
}
}
1 change: 0 additions & 1 deletion templates/dotnet/Package/Services/ServiceTemplate.cs.twig
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
{% import 'dotnet/base/utils.twig' as utils %}

using System;
using System.Collections.Generic;
using System.Linq;
Expand Down
Loading