refit icon indicating copy to clipboard operation
refit copied to clipboard

[BUG] ObjectId is flattened into 5 query string arguments instead of a single string value

Open andrewgunn opened this issue 3 years ago • 3 comments

Describe the bug

MongoDB uses ObjectId as the default primary key. When ObjectId is used on the query string argument, the value is flattened in accordance with the Refit docs:

If you specify an object as a query parameter, all public properties which are not null are used as query parameters.

ObjectId has five public properties:

  • int Timestamp
  • int Machine
  • int Pid
  • int Increment
  • DateTime CreationTime

These properties are rarely used within most applications. CreationTime is probably used the most - it represents the date/time when the ID was generated.

An ObjectId are typically represented as a string e.g. 63028b8339684a16b0ef8b4e and this is what I'd expect to appear on the query string.

[Get("/group")]
Task GroupList([Query] ObjectId id);

>>> /group?Timestamp=1661094220&Machine=15077794&Pid=13168&Increment=16127951&CreationTime=08%2F21%2F2022%2015%3A03%3A40

Steps To Reproduce

See above code sample ☝️.

Expected behavior

>>> /group?id=63028b8339684a16b0ef8b4e

Additional information

The only workaround I can think of is to replace the ObjectId with string. This obviously works but who likes magic strings?

It's also worth noting that this is also the default behaviour when serializing an ObjectId although it's possible to override it using a JsonConverter:

public class ObjectIdConverter : JsonConverter<ObjectId>
{
    public override ObjectId Read(
        ref Utf8JsonReader reader,
        Type typeToConvert,
        JsonSerializerOptions options)
    {
        var value = reader.GetString();

        return ObjectId.TryParse(value, out var result) ? result : ObjectId.Empty;
    }

    public override void Write(
        Utf8JsonWriter writer,
        ObjectId value,
        JsonSerializerOptions options)
    {
        var json = value == default
            ? string.Empty
            : value.ToString();

        writer.WriteStringValue(json);
    }
}

andrewgunn avatar Aug 21 '22 19:08 andrewgunn

I have basically this exact same issue except using StronglyTypedId.

@andrewgunn the approach I'm taking is to use a Default Interface Method to wrap the actual Refit definition so I can expose using the more appropriate type, in your case ObjectId, and internally pass through the underlying string.

Something like this should work for you.

// Default interface method
Task GroupList(ObjectId id) => GroupListInternal(id.ToString());

// Actual Refit definition
[Get("/group")]
internal Task GroupListInternal([Query] string id);

NxSoftware avatar Jan 27 '23 10:01 NxSoftware

I have basically this exact same issue except using StronglyTypedId.

@andrewgunn the approach I'm taking is to use a Default Interface Method to wrap the actual Refit definition so I can expose using the more appropriate type, in your case ObjectId, and internally pass through the underlying string.

Something like this should work for you.

// Default interface method
Task GroupList(ObjectId id) => GroupListInternal(id.ToString());

// Actual Refit definition
[Get("/group")]
internal Task GroupListInternal([Query] string id);

Good idea 👍

andrewgunn avatar Jan 27 '23 11:01 andrewgunn

I also stumbled into this issue with https://github.com/ultimicro/netulid.

GitHub
High quality/performance ULID implementation for .NET - GitHub - ultimicro/netulid: High quality/performance ULID implementation for .NET

ultimaweapon avatar May 21 '23 02:05 ultimaweapon