ChilliCream / graphql-platform

Welcome to the home of the Hot Chocolate GraphQL server for .NET, the Strawberry Shake GraphQL client for .NET and Banana Cake Pop the awesome Monaco based GraphQL IDE.
https://chillicream.com
MIT License
5.26k stars 745 forks source link

Example serializer from the Strawberry Shake documentation doesn't work #7575

Open samneefs opened 1 month ago

samneefs commented 1 month ago

Product

Strawberry Shake

Version

13.9.14

Link to minimal reproduction

https://filebin.net/gs6gkxscspus013d/GraphQlSerializerIssue.zip

Steps to reproduce

  1. Go to https://chillicream.com/docs/strawberryshake/v13/scalars#any-or-json
  2. Copy the example serializer:
    public class MyJsonSerializer : ScalarSerializer<JsonElement, object>
    {
    public MyJsonSerializer(string typeName = BuiltInScalarNames.Any)
        : base(typeName)
    {
    }
    public override object Parse(JsonElement serializedValue)
    {
        // handle the serialization of the JsonElement
    }
    protected override JsonElement Format(object runtimeValue)
    {
        // handle the serialization of the runtime representation in case
        // the scalar is used as a variable.
    }
    }
  3. Paste in a new class
  4. You'll get an error on protected override JsonElement Format(object runtimeValue)
  5. It is impossible to implement this method; it gives compiler error CS0462 since it tries to override two methods: protected abstract TSerialized Format(TRuntime runtimeValue); public object? Format(object? runtimeValue)

What is expected?

A working example

What is actually happening?

Compiler error

Relevant log output

No response

Additional context

No response

samneefs commented 1 month ago

I can't edit the label; it should be "Strawberry shake" instead of "hot chocolate".

samneefs commented 1 month ago

Additionally: the schema.extensions.graphql of the example is also not accepted:

extend scalar JSONObject
  @serializationType(name: "global::System.Object")
  @runtimeType(name: "global::System.Text.Json.JsonElement")

This gives a build error:

Severity    Code    Description Project File    Line    Suppression State   Details
Error (active)  MSB3073 The command "dotnet "C:\Users\**\.nuget\packages\strawberryshake.server\13.5.1\build\..\tools\net8\dotnet-graphql.dll" generate "C:\Users\**\source\repos\AccountantsAccademyConsole\Console2" -o "C:\Users\**\source\repos\AccountantsAccademyConsole\Console2\obj\Debug\net8.0\berry" -n Console2 -a md5 -t" exited with code -532462766. Console2    C:\Users\**\.nuget\packages\strawberryshake.server\13.5.1\build\StrawberryShake.Server.targets  79      

If I change the serializationType to "@serializationType(name: "global::System.String")", it would build, and would almost work, however the exclamation mark causes an error: return _jSONObjectParser.Parse(obj.Value.GetString()!); Without the exclamation mark it would be possible to do ToString on an object and use the parse methode that way: return _jSONObjectParser.Parse(obj.Value.GetString());

Ofcource I didn't actually want to use Object in the end, I wanted a dictionary, however this gives a whole lot of bad string replaces in the build, which ends up with an uncompilable client. eg.

     extend scalar JSONObject
                @serializationType(name: "global::System.String")
                @runtimeType(name: "global::System.Collections.Generic.Dictionary<global::string, global::string>")

This creates invalid methods like: private global::System.Collections.Generic.Dictionary<global::string, global::string>? Deserialize_Dictionary<global ::string, global::string >(global::System.Text.Json.JsonElement? obj)

The workaround that I finaly settled with, is a custom class, with a Dictionary property. The serializer writes to the custom class:

     extend scalar JSONObject
              @serializationType(name: "global::System.Text.Json.JsonElement")
              @runtimeType(name: "Console2.Models.JsonObject")`
public class MyJsonSerializer2 : ScalarSerializer<JsonElement, Console2.Models.JsonObject>
 {
    public MyJsonSerializer2()
            :base("JSONObject")
    {
    }
    public override Console2.Models.JsonObject Parse(JsonElement serializedValue)
    {
            var result = new Console2.Models.JsonObject();
            var json = serializedValue.GetRawText();
            result.Json = json;

            if (string.IsNullOrEmpty(json))
                return result;

            var dict = JsonConvert.DeserializeObject<Dictionary<global::System.String, global::System.String>>(json);
            result.keyValuePairs = dict;

            return result;
    }
    protected override JsonElement Format(Console2.Models.JsonObject runtimeValue)
    {
            var jsonElement = System.Text.Json.JsonSerializer.SerializeToElement(runtimeValue);
            //var json = jsonElement.GetRawText();

            return jsonElement;
    }
 }
michaelstaib commented 1 month ago

Did you create and register a custom serializer for this?

samneefs commented 1 month ago

Did you create and register a custom serializer for this?

The issue is with the custom serializer from the example in the documentation: "MyJsonSerializer". It does't matter if you register it; it cannot be build; it gives compile time errors.

benitoanagua commented 1 month ago

In this context

schema.graphql

type atractivos {
  id: ID!
  status: String
  sort: Int
  user_created: String
  date_created: Date
  date_created_func: datetime_functions
  user_updated: String
  date_updated: Date
  date_updated_func: datetime_functions
  nombre: String
  municipio: JSON
  municipio_func: count_functions
  descripcion: String
  ubicacion_gps: GraphQLGeoJSON
  slug: String!
  galeria_fotos(filter: atractivos_files_filter sort: [String] limit: Int offset: Int page: Int search: String): [atractivos_files]
  galeria_fotos_func: count_functions
}
query GetHero {
  atractivos(limit: 3) {
    nombre
    ubicacion_gps
    foto: galeria_fotos(limit: 1) {
      directus_files_id
    }
  }
}
{
  "data": {
    "atractivos": [
      {
        "nombre": "Generic Cotton Mouse",
        "ubicacion_gps": {
          "type": "Point",
          "coordinates": [
            -64.6976398964243,
            -21.552152916966904
          ]
        },
        "foto": [
          {
            "directus_files_id": "4d7805c7-a927-4aa4-b2ae-74c4ea8fd38d"
          }
        ]
      },
      ...
    ]
  }
}

schema.extensions.graphql

scalar _KeyFieldSet

directive @key(fields: _KeyFieldSet!) on SCHEMA | OBJECT

directive @serializationType(name: String!) on SCALAR

directive @runtimeType(name: String!) on SCALAR

directive @enumValue(value: String!) on ENUM_VALUE

directive @rename(
  name: String!
) on INPUT_FIELD_DEFINITION | INPUT_OBJECT | ENUM | ENUM_VALUE

extend schema @key(fields: "id")

extend scalar GraphQLGeoJSON
  @serializationType(name: "global::System.Text.Json.JsonElement")
  @runtimeType(name: "global::Website.Models.GeoJsonObject")

GeoJsonObject.cs

namespace Website.Models
{
    public class GeoJsonObject
    {
        public string? Type { get; set; }
        public List<double>? Coordinates { get; set; }
    }
}

This has worked for me

using StrawberryShake.Serialization;
using Website.Models;
using Json = System.Text.Json;

namespace Website.Serializers
{
    public class GraphQLGeoJSONSerializer : ScalarSerializer<Json.JsonElement, GeoJsonObject>
    {
        public GraphQLGeoJSONSerializer()
            : base("GraphQLGeoJSON") { }

        public override GeoJsonObject Parse(Json.JsonElement serializedValue)
        {
            var geoJson = new GeoJsonObject
            {
                Type = serializedValue.GetProperty("type").GetString(),
                Coordinates = serializedValue
                    .GetProperty("coordinates")
                    .EnumerateArray()
                    .Select(coord => coord.GetDouble())
                    .ToList(),
            };

            return geoJson;
        }

        protected override Json.JsonElement Format(GeoJsonObject runtimeValue)
        {
            var jsonString = Json.JsonSerializer.Serialize(runtimeValue);
            return Json.JsonSerializer.Deserialize<Json.JsonElement>(jsonString);
        }
    }
}

// Register custom serializer 
// builder.Services.AddSerializer<GraphQLGeoJSONSerializer>();