JSon.Net Serialize result of yield return - c#

I am trying to return some data to a webservice using json and the JSon.Net library. One of my functions is an iterator method that lists data using yield return. When I try to serialize this return value, I am getting an invalid operation exception
I am using
string jsonEncoded = JsonConvert.SerializeObject(ret, Formatting.Indented); to serialize the return value.
The full stack trace of the exception is:
System.InvalidOperationException: This operation is only valid on generic types.
at System.RuntimeType.GetGenericTypeDefinition()
at Newtonsoft.Json.Serialization.JsonArrayContract..ctor(Type underlyingType) in c:\Temp\Json\Working\Newtonsoft.Json\Src\Newtonsoft.Json\Serialization\JsonArrayContract.cs:line 148
at Newtonsoft.Json.Serialization.DefaultContractResolver.CreateArrayContract(Type objectType) in c:\Temp\Json\Working\Newtonsoft.Json\Src\Newtonsoft.Json\Serialization\DefaultContractResolver.cs:line 686
at Newtonsoft.Json.Serialization.DefaultContractResolver.CreateContract(Type objectType) in c:\Temp\Json\Working\Newtonsoft.Json\Src\Newtonsoft.Json\Serialization\DefaultContractResolver.cs:line 800
at Newtonsoft.Json.Serialization.DefaultContractResolver.ResolveContract(Type type) in c:\Temp\Json\Working\Newtonsoft.Json\Src\Newtonsoft.Json\Serialization\DefaultContractResolver.cs:line 232
at Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.GetContractSafe(Object value) in c:\Temp\Json\Working\Newtonsoft.Json\Src\Newtonsoft.Json\Serialization\JsonSerializerInternalWriter.cs:line 83
at Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.Serialize(JsonWriter jsonWriter, Object value, Type objectType) in c:\Temp\Json\Working\Newtonsoft.Json\Src\Newtonsoft.Json\Serialization\JsonSerializerInternalWriter.cs:line 67
at Newtonsoft.Json.JsonSerializer.SerializeInternal(JsonWriter jsonWriter, Object value, Type objectType) in c:\Temp\Json\Working\Newtonsoft.Json\Src\Newtonsoft.Json\JsonSerializer.cs:line 753
at Newtonsoft.Json.JsonSerializer.Serialize(JsonWriter jsonWriter, Object value, Type objectType) in c:\Temp\Json\Working\Newtonsoft.Json\Src\Newtonsoft.Json\JsonSerializer.cs:line 668
at Newtonsoft.Json.JsonConvert.SerializeObject(Object value, Type type, Formatting formatting, JsonSerializerSettings settings) in c:\Temp\Json\Working\Newtonsoft.Json\Src\Newtonsoft.Json\JsonConvert.cs:line 921
at Newtonsoft.Json.JsonConvert.SerializeObject(Object value, Formatting formatting, JsonSerializerSettings settings) in c:\Temp\Json\Working\Newtonsoft.Json\Src\Newtonsoft.Json\JsonConvert.cs:line 893
at Newtonsoft.Json.JsonConvert.SerializeObject(Object value, Formatting formatting) in c:\Temp\Json\Working\Newtonsoft.Json\Src\Newtonsoft.Json\JsonConvert.cs:line 837
at AladdinWeb.Handlers.AladdinHandler.ProcessRequest(HttpContext context) in C:\Users\mehrlich\Projects\AladdinWeb\AladdinWeb\Server\Handlers\AladdinHandler.cs:line 85 [ 15/04/2013 11:24:24.68135 ]
The signature of the iterator method is:
public IEnumerable<dynamic> FunctionName() { ... }
As of now, I have a temporary solution in place by checking for calls to this function and calling ToList on the return value. This serializes just fine, but it is kind of an ugly solution since I need to have a special case for it (and any other iterator methods I might add). My guess is that this has to do with the IEnumerable not being enumerated.
Can I get Json.Net to serialize the result of my iterator functions or will I always need a special case like this? Let me know if any more information or source code is needed and I will post it.
More Info: I am using the .Net framework version 4.0 and I am using Json.Net version 5.0r2
Abridged Source Code of the Iterator Method
public IEnumerable<dynamic> FunctionName()
{
var methodList = typeof(Targets).GetMethods();
foreach (var m in methodList)
{
dynamic info = new ExpandoObject();
info.Name = m.Name;
info.Parameters = from param in m.GetParameters()
select param.Name;
yield return info;
}
}
Source Code of Method Call
...
object ret = null;
if (q == "FunctionName")
{
ret = FunctionName(); // This causes an exception to be thrown later
// ret = FunctionName().ToList(); // This does NOT throw an exception later
}
else
{
ret = DoOtherStuff(q, request);
}
// Serialize the result to JSON
// This line throws the exception
string jsonEncoded = JsonConvert.SerializeObject(ret, Formatting.Indented);
...

This issue is a bug with JSon.Net version 5.0r2, updating to 5.0r3 will fix this and in fact was the only reason for the 5.0r3 release.

Related

How to find out what type a JsonValue is in System.Text.Json

So when I have a JsonNode I can just ask if it's a JsonObject or a JsonArray and work with those. But when the node is an actual value, how do I know whether it's a string, number or boolean?
Of course I could just try and parse the value, but then a number transmitted in a string would become a number instead of a string which I'd like to avoid.
I'm using System.Text.Json with .NET 6.
From the source, it looks like a JsonValue just wraps a JsonElement. So you can do .GetValue<JsonElement>() (which passes this check), and then inspect its ValueKind property.
The accepted answer only works for specific use cases, namely that the node in question is of type JsonValue.
I propose a better option is to first test for the basic kind of types there are in JSON.
someObject["SomeNode"] is JsonArray
someObject["SomeNode"] is JsonObject
someObject["SomeNode"] is JsonValue
if the object is of type JsonValue one can use tryGetvalue to directly test for the expected value type
someObject["SomeNode"].AsValue().TryGetValue<someType>(out someType result)
TryGetValue returns true if it the value was parseble as the requested type.
If the expected type is completely unknown or variable (ugh), you could use the
someObject["SomeNode"].GetValue<JsonElement>().ValueKind
trick. But that only works for distinquishing between int and string and the bool values. Trying this on an array will give an exception. Therefore you first have to test with the "is" style syntax above.
Each JsonProperty has two properties - Name and Value of Type JsonElement. JsonElement has an Enum property named ValueKind which can help you determine what of what data type your JSON value is.
You can get JsonProperties by calling .EnumerateObject() on your JsonElement. You can work with your Json document as a JsonElement instead of JsonObject.
The following code works in .NET fiddle with .NET 6.
Note:
if your JsonValue ultimately came from a JsonNode.Parse*(...), then your JsonValue will contain a "JSON type".
if your JsonValue was created using JsonValue.Create() or an implicit conversion, then your JsonValue will contain a "CLR type".
The following code returns an approximate "CLR type" for "JSON types", because of the next point.
JsonValue.GetValue<T>() only does type conversion if JsonValue contains a "JSON type".
JsonValue.GetValue<object>() conveniently returns the underlying value, which is a JsonElement if it is a "JSON type".
public static class JsonValueExtensions
{
public static Type GetValueType(this JsonValue jsonValue)
{
var value = jsonValue.GetValue<object>();
if (value is JsonElement element)
{
return element.ValueKind switch
{
JsonValueKind.False => typeof(bool),
JsonValueKind.True => typeof(bool),
JsonValueKind.Number => typeof(double),
JsonValueKind.String => typeof(string),
var _ => typeof(JsonElement),
};
}
return value.GetType();
}
}
Depending on the type of JsonElement returned you have to handle it differently.
My case was that the returned element was ValueKind = Array : "[[47.751]]"
So in order to get it I did created this method
private object GetValueFromJsonElement(WorkbookRange range)
{
var element = range.Values.RootElement.EnumerateArray().First()[0];
switch (element.ValueKind)
{
case JsonValueKind.Number:
return element.GetDouble();
case JsonValueKind.String:
return element.GetString();
case JsonValueKind.True:
case JsonValueKind.False:
return element.GetBoolean();
default:
throw new InvalidOperationException("The Value Type returned is not handled");
}
}

Reading raw input inside JsonConverter.ReadJson [duplicate]

How can I efficiently get full json string in JsonConverter.ReadJson() ?
I can do:
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) {
var json = JObject.Load(reader).ToString(Formatting.None);
However that seems very inefficient as I serialize and deserialize for no reason
Any better way ?
ReadJson() must fully parse the JSON being read so that the JSON is confirmed to be well-formed and the JsonReader is correctly positioned at the end of the current value upon exit. However, it is not necessary to load the entire JSON into an intermediate JObject hierarchy simply to re-convert it to a JSON string. Instead, you may be able to get better performance by using JRaw.Create():
var json = JRaw.Create(reader).ToString();
As can be seen in the reference source, this method streams directly from the incoming JsonReader to a StringWriter - without loading into an intermediate JToken hierarchy and re-serializing - by using JsonWriter.WriteToken(JsonReader):
public static JRaw Create(JsonReader reader)
{
using (StringWriter sw = new StringWriter(CultureInfo.InvariantCulture))
using (JsonTextWriter jsonWriter = new JsonTextWriter(sw))
{
jsonWriter.WriteToken(reader);
return new JRaw(sw.ToString());
}
}
The resulting JRaw simply encapsulates that string in its Value. (Of course, there is no guarantee that the resulting JSON represents an object, only that it represents well-formed JSON.)
Note that JsonTextReader will automatically recognize and parse dates and times in common formats as DateTime objects, and also parse floating point values as double. If you need the "most literal" JSON string you may want to suppress DateTime recognition and/or parse floating point values as decimal. The following extension method, modeled on JRaw.Create(), does the job:
public static string ReadOuterJson(this JsonReader reader, Formatting formatting = Formatting.None, DateParseHandling? dateParseHandling = null, FloatParseHandling? floatParseHandling = null)
{
// If you would prefer a null JSON value to return an empty string, remove this line:
if (reader.TokenType == JsonToken.Null)
return null;
var oldDateParseHandling = reader.DateParseHandling;
var oldFloatParseHandling = reader.FloatParseHandling;
try
{
if (dateParseHandling != null)
reader.DateParseHandling = dateParseHandling.Value;
if (floatParseHandling != null)
reader.FloatParseHandling = floatParseHandling.Value;
using (var sw = new StringWriter(CultureInfo.InvariantCulture))
using (var jsonWriter = new JsonTextWriter(sw) { Formatting = formatting })
{
jsonWriter.WriteToken(reader);
return sw.ToString();
}
}
finally
{
reader.DateParseHandling = oldDateParseHandling;
reader.FloatParseHandling = oldFloatParseHandling;
}
}
And then call it like, e.g.:
var json = reader.ReadOuterJson(dateParseHandling: DateParseHandling.None);
For details on why this may be necessary, see:
Json.NET interprets and modifies ISO dates when deserializing to JObject #862.
JObject.Parse modifies end of floating point values.

Using Json.net to deserialize to any unkown type works for objects but not value types

I've implemented a Json Serializer based on Json.net to accept any object type and serialize it (for placement into my cache)
The cache interface doesn't allow me to speficy the type, so When I retrieve from the cache I need to dynamically create the type from the meta information.
Which works well for objects, the problem I am now facing is that i doesn't work for value types, I will get an exception saying something along the lines of cannot cast JValue to JObject.
My question is how can I cater for value types as well as object types? It would be great if there was a TryParse for a JObject, which I could write myself, but feel like I am going down a rabbit hole?
What is the best way to achieve this?
My code is as follows, settings for Json.net:
_settings = new JsonSerializerSettings
{
ContractResolver = new CamelCasePropertyNamesContractResolver(),
NullValueHandling = NullValueHandling.Ignore,
DateTimeZoneHandling = DateTimeZoneHandling.Utc,
TypeNameHandling = TypeNameHandling.All
};
_settings.Converters.Add(new StringEnumConverter());
The set function (serialize):
public void Put(string cacheKey, object toBeCached, TimeSpan cacheDuration)
{
_cache.Set(cacheKey, JsonConvert.SerializeObject(toBeCached, _settings), cacheDuration);
}
And the get (deserialize):
public object Get(string cacheKey)
{
try
{
var value = _cache.Get(cacheKey);
if (!value.HasValue)
{
return null;
}
var jobject = JsonConvert.DeserializeObject<JObject>(value);
var typeName = jobject?["$type"].ToString();
if (typeName == null)
{
return null;
}
var type = Type.GetType(typeName);
return jobject.ToObject(type);
}
catch (Exception e)
{
// Todo
return null;
}
}
You need to parse to a JToken rather than a JObject, then check to see if the returned type is a JValue containing a JSON primitive:
public static object Get(string value)
{
var jToken = JsonConvert.DeserializeObject<JToken>(value);
if (jToken == null)
return null;
else if (jToken is JValue)
{
return ((JValue)jToken).Value;
}
else
{
if (jToken["$type"] == null)
return null;
// Use the same serializer settings as used during serialization.
// Ideally with a proper SerializationBinder that sanitizes incoming types as suggested
// in https://www.newtonsoft.com/json/help/html/T_Newtonsoft_Json_TypeNameHandling.htm
var _settings = new JsonSerializerSettings
{
ContractResolver = new CamelCasePropertyNamesContractResolver(),
NullValueHandling = NullValueHandling.Ignore,
DateTimeZoneHandling = DateTimeZoneHandling.Utc,
TypeNameHandling = TypeNameHandling.All,
Converters = { new StringEnumConverter() },
//SerializationBinder = new SafeSerializationBinder(),
};
// Since the JSON contains a $type parameter and TypeNameHandling is enabled, if we deserialize
// to type object the $type information will be used to determine the actual type, using Json.NET's
// serialization binder: https://www.newtonsoft.com/json/help/html/SerializeSerializationBinder.htm
return jToken.ToObject(typeof(object), JsonSerializer.CreateDefault(_settings));
}
}
Note, however, that type information for primitives will not get precisely round-tripped:
Integers will be returned as a long (or a BigInteger if larger than long.MaxValue).
Floating point values will be returned as a double or a decimal, depending upon the setting of JsonSerializerSettings.FloatParseHandling -- either FloatParseHandling.Double or FloatParseHandling.Decimal.
JSON strings that "look like" dates will get converted to DateTime, DateTimeOffset, or left as strings, depending on the setting JsonSerializerSettings.DateParseHandling.
enum names will be converted to strings.
If you need to round-trip type information for primitives, consider using TypeWrapper<T> from Deserialize specific enum into system.enum in Json.Net to encapsulate your root objects.
Finally, if there is any chance you might be deserializing untrusted JSON (and if you are deserializing from a file or from the internet then you certainly are), please note the following caution from the Json.NET documentation:
TypeNameHandling should be used with caution when your application deserializes JSON from an external source. Incoming types should be validated with a custom SerializationBinder when deserializing with a value other than None.
For a discussion of why this may be necessary, see TypeNameHandling caution in Newtonsoft Json, How to configure Json.NET to create a vulnerable web API, and Alvaro Muñoz & Oleksandr Mirosh's blackhat paper https://www.blackhat.com/docs/us-17/thursday/us-17-Munoz-Friday-The-13th-JSON-Attacks-wp.pdf

Generating IL for Nullable<T> serialization?

I'm writing my own serializer that emits IL to generate the [de]serialization codes.
For nullables, I thought I could generate the following (take int? as an ex) (assuming we already generated methods to [de]serialize int):
public static void Serialize(Stream stream, int? value, object context)
{
Serialize(stream, (int)value, context);
}
public static void Deseiralize(Stream stream, out int? value, object context)
{
int tmp;
Deserialize(stream, out tmp, context);
value = tmp;
}
Here's how I generate that:
public override void GenSerializationCode(Type type)
{
var underlyingType = Nullable.GetUnderlyingType(type);
var serialize = GetSerializeCall(underlyingType);
// Serialize(stream, (UnderlyingType)value, context);
emit.ldarg_0()
.ldarg_1()
.unbox_any(underlyingType)
.ldarg_2()
.call(serialize)
.ret();
}
public override void GenDeserializationCode(Type type)
{
var underlyingType = Nullable.GetUnderlyingType(type);
var deserialize = GetDeserializeCall(underlyingType);
// UnderlyingType tmp; Deserialize(stream, out tmp, context);
var tmp = emit.declocal(underlyingType);
emit.ldarg_0()
.ldloca_s(tmp)
.ldarg_2()
.call(deserialize);
// value = tmp;
emit.ldarg_1()
.ldloc_s(tmp)
.stind_ref()
.ret();
}
I also generate an assembly for debugging. I load it up in ILSpy and the C# code looks exactly like what I had in mind. But peverify had something else to say...
I thought about it for a minute, then realized that Nullable<T> is a struct, so I should use Ldarga instead of Ldarg so I changed my ldarg_1() to ldarga(1)
Now peverify gives:
[IL]: Error: [C:\Users\vexe\Desktop\MyExtensionsAndHelpers\Solution\CustomSerializer\bin\Release\SerTest.dll : FastSerializer::Serialize][offset 0x00000007][found address of value 'System.Nullable`1[System.Int32]'] Expected an ObjRef on the stack.
I thought it's something to do with Nullable<T> conversion operators so I tried the Value property:
var underlyingType = Nullable.GetUnderlyingType(type);
var serialize = GetSerializeCall(underlyingType);
var getValue = type.GetProperty("Value").GetGetMethod();
// Serialize(stream, value.get_Value(), context);
emit.ldarg_0()
.ldarga(1)
.call(getValue)
.ldarg_2()
.call(serialize)
.ret();
peverify is happy about this!
The question is, why didn't the explicit operator from T to Nullable<T> kick in previously when casting the nullable to its underlying type?
Also, I wasn't able to get rid of the error in Deserialize even when using Ldarga instead of Ldarg when doing value = tmp;- I guess I could try what the implicit conversion is doing. i.e. value = new Nullable<int>(tmp); but I would like to find out what I did wrong.
Note: 'emit' is just a helper I use to generate IL. It uses an ILGenerator internally and returns itself after each operation so I can chain together calls.
EDIT: here's the final code that worked, with notes and all.
// Note:
// 1- IL doesn't know anything about implicit/explicit operators
// so we can't make use of the T to Nullable<T> nor Nullable<T> to T operators
// that's why we have to use the Value property when serializing and the ctor when deserializing
// 2- Nullable<T> is a struct
// so we use ldarga when calling the property getter when serializing (the property getter is an instance method, so the first argument is always the 'this', but since we're dealing with structs we have to pass 'this' by ref hence ldarga)
// then use stobj opcode when constructing an instance when deserializing
public override void GenSerializationCode(Type type)
{
var underlyingType = Nullable.GetUnderlyingType(type);
var serialize = ctx.GetSerializeCall(underlyingType);
var getValue = type.GetProperty("Value").GetGetMethod();
// Serialize(stream, value.get_Value(), ctx);
emit.ldarg_0()
.ldarga(1)
.call(getValue)
.ldarg_2()
.call(serialize)
.ret();
}
public override void GenDeserializationCode(Type type)
{
var underlyingType = Nullable.GetUnderlyingType(type);
var deserialize = ctx.GetDeserializeCall(underlyingType);
// UnderlyingType tmp; Deserialize(stream, out tmp, ctx);
var tmp = emit.declocal(underlyingType);
emit.ldarg_0()
.ldloca_s(tmp)
.ldarg_2()
.call(deserialize);
// value = new Nullable<UnderlyingType>(tmp);
var ctor = type.GetConstructor(new Type[] { underlyingType });
emit.ldarg_1()
.ldloc_s(tmp)
.newobj(ctor)
.stobj(type)
.ret();
}
}
Explicit and implicit conversions are a purely C# concept.
IL does not have any special awareness of nullable types (except for boxing them into Objects); you need to explicitly use .Value or call the ctor.
For examples, look at the IL generated by the C# compiler.
Operators is only for the C# and VB.NET compilers. IL and IL generations knows nothing about it.

Serialize hashtable using Json.Net

I have a hashtable whose keys are of type integer, however when deserializing using json.net the keys come back as strings, is there a way to keep the key type on hashtable using json.net serialization/deserialization? This hashtable is a property of the type 'MyType'
var settings = new JsonSerializerSettings();
settings.TypeNameHandling = TypeNameHandling.Objects;
string json = JsonConvert.SerializeObject(o, Formatting.Indented, settings);
mo = JsonConvert.DeserializeObject<MyType>(json, new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.Objects });
public Hashtable jsonViews
{
get { return mViews; }
set { mViews = value; }
}
The problem is that the System.Collections.Hashtable isn't strongly typed - it will hold any type of object, and JSON.NET is most likely serialising the string representation of your hashtable contents.
Before you spend too much time massaging the JSON.NET serializer/deserializer to compensate for this, you might want to consider switching your Hashtable out for a Dictionary<TKey, TValue>. It's almost identical in terms of performance, but gives you the advantage and safety of a strongly-typed collection.
A strongly-typed collection will resolve your Json.NET deserialization issues, as Json.NET can infer the type from the dictionary.
The use of Dictionary<TKey,TValue> over Hashtable is discussed here.
Here's a generic static extension method that I wrote to help me with this problem. I basically wanted this code to never blow up even if the data is somehow corrupted.
public static T Read<T>(this Hashtable hash, string key)
{
if (!hash.ContainsKey(key))
return default;
if (hash[key] is T)
return (T)hash[key];
if (hash[key] is JToken token)
return token.ToObject<T>();
try
{
return (T)Convert.ChangeType(hash[key], typeof(T));
}
catch (InvalidCastException)
{
Debug.LogWarning($"{key} had the wrong type of {hash[key].GetType()}");
return default;
}
}

Categories