Skip to content

Commit 3368694

Browse files
Fix boolean value parsing in API responses
1 parent dcd3bc0 commit 3368694

File tree

7 files changed

+152
-67
lines changed

7 files changed

+152
-67
lines changed

CloudinaryDotNet.Tests/UploadPresetsTest.cs

+30-1
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,35 @@ public void TestListUploadPresets()
3939
Assert.AreEqual(OnSuccessStr, result.Presets.First().OnSuccess);
4040
}
4141

42+
[Test]
43+
public void TestListUploadPresetsBooleanValues()
44+
{
45+
const string responseStr = @"
46+
{
47+
""presets"": [
48+
{ ""name"": ""int-str"", ""settings"": { ""overwrite"": ""1"" } },
49+
{ ""name"": ""bool"", ""settings"": { ""overwrite"": true } },
50+
{ ""name"": ""bool-str"", ""settings"": { ""overwrite"": ""true"" } },
51+
{ ""name"": ""bool-str-false"", ""settings"": { ""overwrite"": ""false"" } },
52+
{ ""name"": ""empty"", ""settings"": { ""overwrite"": """" } },
53+
{ ""name"": ""default"", ""settings"": { ""no_overwrite"": true } },
54+
]
55+
}";
56+
57+
var localCloudinaryMock = new MockedCloudinary(responseStr);
58+
59+
var result = localCloudinaryMock.ListUploadPresets();
60+
61+
localCloudinaryMock.AssertHttpCall(SystemHttp.HttpMethod.Get, uploadPresetsRootUrl);
62+
63+
Assert.IsTrue(result.Presets.First().Settings.Overwrite);
64+
Assert.IsTrue(result.Presets[1].Settings.Overwrite);
65+
Assert.IsTrue(result.Presets[2].Settings.Overwrite);
66+
Assert.IsFalse(result.Presets[3].Settings.Overwrite);
67+
Assert.IsNull(result.Presets[4].Settings.Overwrite);
68+
Assert.IsNull(result.Presets[5].Settings.Overwrite);
69+
}
70+
4271
[TestCase("x-featureratelimit-limit", 123)]
4372
[TestCase("X-FeatureRateLimit-Limit", 123)]
4473
public void TestFeatureRateLimitLimitHeader(string headerName, long headerValue)
@@ -47,7 +76,7 @@ public void TestFeatureRateLimitLimitHeader(string headerName, long headerValue)
4776
var headers = message.Headers;
4877

4978
headers.Add(headerName, headerValue.ToString());
50-
79+
5180
var localCloudinaryMock = new MockedCloudinary(httpResponseHeaders: headers);
5281

5382
var result = localCloudinaryMock.ListUploadPresets();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
namespace CloudinaryDotNet.Actions
2+
{
3+
using System;
4+
using System.Globalization;
5+
using Newtonsoft.Json;
6+
7+
/// <summary>
8+
/// Custom JSON converter to safely parse boolean values that might come as strings.
9+
/// </summary>
10+
public class SafeBooleanConverter : JsonConverter
11+
{
12+
/// <summary>
13+
/// Gets a value indicating whether this <see cref="SafeBooleanConverter"/> can write JSON.
14+
/// </summary>
15+
public override bool CanWrite => false;
16+
17+
/// <summary>
18+
/// Determines whether this instance can convert the specified object type.
19+
/// </summary>
20+
/// <param name="objectType">Type of the object.</param>
21+
/// <returns>True if this instance can convert the specified object type; otherwise, false.</returns>
22+
public override bool CanConvert(Type objectType)
23+
{
24+
// Decide which types you want this converter to handle
25+
return objectType == typeof(bool) || objectType == typeof(bool?);
26+
}
27+
28+
/// <summary>
29+
/// Reads the JSON representation of the object.
30+
/// </summary>
31+
/// <param name="reader">The <see cref="JsonReader"/> to read from.</param>
32+
/// <param name="objectType">Type of the object.</param>
33+
/// <param name="existingValue">The existing value of the object being read.</param>
34+
/// <param name="serializer">The calling serializer.</param>
35+
/// <returns>The object value.</returns>
36+
public override object ReadJson(
37+
JsonReader reader,
38+
Type objectType,
39+
object existingValue,
40+
JsonSerializer serializer)
41+
{
42+
// We'll parse everything into a 'bool?' and decide how to return
43+
// based on whether the target is bool or bool?.
44+
bool? parsedValue = null;
45+
46+
if (reader.TokenType == JsonToken.Null ||
47+
(reader.Value is string strVal && string.IsNullOrWhiteSpace(strVal)))
48+
{
49+
parsedValue = null;
50+
}
51+
52+
// Handle Boolean token directly: e.g., true or false
53+
else if (reader is { TokenType: JsonToken.Boolean, Value: bool boolValue })
54+
{
55+
parsedValue = boolValue;
56+
}
57+
58+
// Handle integer "0" or "1"
59+
else if (reader is { TokenType: JsonToken.Integer, Value: not null })
60+
{
61+
var intValue = Convert.ToInt64(reader.Value, CultureInfo.InvariantCulture);
62+
parsedValue = intValue == 1;
63+
}
64+
65+
// Handle string values: "0", "1", "true", "false", etc.
66+
else if (reader is { TokenType: JsonToken.String, Value: string stringValue })
67+
{
68+
switch (stringValue)
69+
{
70+
case "0":
71+
parsedValue = false;
72+
break;
73+
case "1":
74+
parsedValue = true;
75+
break;
76+
default:
77+
if (bool.TryParse(stringValue, out var boolFromString))
78+
{
79+
parsedValue = boolFromString;
80+
}
81+
else
82+
{
83+
throw new JsonSerializationException(
84+
$"Unable to parse \"{stringValue}\" as a boolean value.");
85+
}
86+
87+
break;
88+
}
89+
}
90+
else
91+
{
92+
// Unexpected token => throw
93+
throw new JsonSerializationException(
94+
$"Unexpected token {reader.TokenType} when parsing a boolean.");
95+
}
96+
97+
// If we're targeting bool? => return the parsedValue (can be null or bool).
98+
// If we're targeting bool => return false if parsedValue is null, else the bool value.
99+
if (objectType == typeof(bool?))
100+
{
101+
return parsedValue;
102+
}
103+
104+
// objectType == typeof(bool)
105+
return parsedValue ?? false;
106+
}
107+
108+
/// <summary>
109+
/// Writes the JSON representation of the object.
110+
/// </summary>
111+
/// <param name="writer">The <see cref="JsonWriter"/> to write to.</param>
112+
/// <param name="value">The value.</param>
113+
/// <param name="serializer">The calling serializer.</param>
114+
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
115+
{
116+
throw new NotImplementedException("Unnecessary because this converter is used primarily for deserialization.");
117+
}
118+
}
119+
}

CloudinaryDotNet/Actions/PresetsUpload/SafeBooleanConverter.cs

-65
This file was deleted.

CloudinaryDotNet/ApiShared.Internal.cs

+3-1
Original file line numberDiff line numberDiff line change
@@ -256,7 +256,9 @@ private static T CreateResultFromStream<T>(Stream s, HttpStatusCode statusCode)
256256
using var streamReader = new StreamReader(s);
257257
using var jsonReader = new JsonTextReader(streamReader);
258258
var jsonObj = JToken.Load(jsonReader);
259-
var result = jsonObj.ToObject<T>();
259+
var serializer = new JsonSerializer();
260+
serializer.Converters.Add(new SafeBooleanConverter());
261+
var result = jsonObj.ToObject<T>(serializer);
260262
result.JsonObj = jsonObj;
261263

262264
return result;

0 commit comments

Comments
 (0)