Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

json: 🐛Fix stack overflow deserializing empty JSON #1293

Merged

Conversation

shaeussler
Copy link
Contributor

@shaeussler shaeussler commented Jul 21, 2023

Fixes #1292

The call to "token.ToObject(serializer)" with an empty or invalid json calls JsonConvert.DeserializeObject again and results in infinite recursion and stack overflow.

Changes

  • Throw JsonSerializationException on invalid JSON in UnitsNetIQuantityJsonConverter
    • Include helpful info in exception, link to wiki, truncated JSON token in message and full JSON token in Data["JsonToken"] property.
  • Add tests for new behavior

Copy link
Owner

@angularsen angularsen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good, a minor suggestion

@angularsen angularsen changed the title fix: https://github.com/angularsen/UnitsNet/issues/1292 Fix stack overflow deserializing empty JSON Jul 21, 2023
@angularsen angularsen changed the title Fix stack overflow deserializing empty JSON json: Fix stack overflow deserializing empty JSON Jul 21, 2023
@angularsen angularsen changed the title json: Fix stack overflow deserializing empty JSON json: 🐛Fix stack overflow deserializing empty JSON Jul 21, 2023
@shaeussler
Copy link
Contributor Author

Why do you call "token.ToObject(serializer);" inside ReadJson?
Here is another fix:

            if (valueUnit == null)
            {
                return existingValue; // token.ToObject<IQuantity>(serializer);
            }

@angularsen
Copy link
Owner

Why do you call "token.ToObject(serializer);" inside ReadJson? Here is another fix:

I think this was bug. UnitsNetIComparableJsonConverter uses UnitsNetBaseJsonConverter<T>.CreateLocalSerializer to create a new serializer internally, excluding itself.

I'm guessing a similar approach was intended here, but we should not need to deserialize a second time here and can instead just return existingValue as you proposed.

I pushed some changes in that direction, take a look

@angularsen
Copy link
Owner

angularsen commented Jul 21, 2023

It's hard to find any official documentation on how to handle errors in deserialization, but I changed it to explicitly throw an exception if it fails to deserialize the JSON.

It seems an exception is the convention when trying to deserialize "{}" to DateTime?, DateTime and other built-in supported types in json.net, rather than returning null or the existing value.

@shaeussler
Copy link
Contributor Author

Throwing a JsonSerializationException in case of an empty json is ok in my eyes. I added the token to the error message.

@angularsen
Copy link
Owner

angularsen commented Jul 24, 2023

Great! JSON tokens can potentially be huge, maybe we should truncate it to a max length of say 100?

This example is 50 long:
{ "Value": 5.9999999, "Unit": "LengthUnit.Meter" }

We can also include the full token in the Data dictionary property instead of in the message, which allows debuggers to view it as well as log frameworks to log it if configured so.

We use this extension method in a separate project, which we could add as an internal class in UnitsNet:

    internal static class StringExtensions
    {
        /// <summary>
        /// Truncates a string to the given length, with truncation suffix.
        /// </summary>
        /// <param name="value">The string.</param>
        /// <param name="maxLength">The max length, including <paramref name="truncationSuffix"/>.</param>
        /// <param name="truncationSuffix">Suffix to append if truncated, defaults to "…".</param>
        /// <returns>The truncated string if longer, otherwise the original string.</returns>
        [return: NotNullIfNotNull("value")]
        public static string? Truncate(this string? value, int maxLength, string truncationSuffix = "…")
        {
            return value?.Length > maxLength
                ? value.Substring(0, maxLength - truncationSuffix.Length) + truncationSuffix
                : value;
        }
}

@angularsen angularsen enabled auto-merge (squash) July 24, 2023 19:33
@angularsen angularsen merged commit f008252 into angularsen:master Jul 24, 2023
@angularsen
Copy link
Owner

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Deserialization of an empty unit cause a stackoverflow
2 participants