I want so serialize/deserialize the following model:
public class ReadabilitySettings
{
public ReadabilitySettings() {
}
private bool _reababilityEnabled;
public bool ReadabilityEnabled {
get {
return _reababilityEnabled;
}
set {
_reababilityEnabled = value;
}
}
private string _fontName;
public string FontName {
get {
return _fontName;
}
set {
_fontName = value;
}
}
private bool _isInverted;
public bool IsInverted {
get {
return _isInverted;
}
set {
_isInverted = value;
}
}
public enum FontSizes
{
Small = 0,
Medium = 1,
Large = 2
}
private FontSizes _fontSize;
public FontSizes FontSize { get
{
return _fontSize;
}
set
{
_fontSize = value;
}
}
}
}
I have a list containing instances of the following object:
public class CacheItem<T>
{
public string Key { get; set; }
public T Value { get; set; }
}
I populate the list like so:
list.Add(new CacheItem<ReadabilitySettings>() { Key = "key1", Value = InstanceOfReadabilitySettings };
When I want to serialize this list I call:
var json = JsonConvert.SerializeObject (list);
This works fine. No errors. It gives the following json:
[{"Key":"readbilitySettings","Value":{"ReadabilityEnabled":true,"FontName":"Lora","IsInverted":true,"FontSize":2}}]
When I want to deserialize the list I call:
var list = JsonConvert.DeserializeObject<List<CacheItem<object>>> (json);
This gives me a list of CacheItem's with it's Value property set to a JObject. No errors so far.
When I want the actual instance of ReadabilitySettings I call:
var settings = JsonConvert.DeserializeObject<ReadabilitySettings> (cacheItem.Value.ToString ());
I have to call this since the cacheItem.Value is set to a json string, not to an instance of ReadabilitySettings. The json string is:
{{ "ReadabilityEnabled": true, "FontName": "Lora", "IsInverted": true, "FontSize": 2 }} Newtonsoft.Json.Linq.JObject
Then I get this error: "Error setting value to 'ReadabilityEnabled' on 'Reflect.Mobile.Shared.State.ReadabilitySettings'."
What am I missing? Thanks!
EDIT------
This is the method that throws the error:
public T Get<T> (string key)
{
var items = GetCacheItems (); // this get the initial deserialized list of CacheItems<object> with its value property set to a JObject
if (items == null)
throw new CacheKeyNotFoundException ();
var item = items.Find (q => q.Key == key);
if (item == null)
throw new CacheKeyNotFoundException ();
var result = JsonConvert.DeserializeObject<T> (item.Value.ToString ()); //this throws the error
return result;
}
I've just tried this, which is pretty much copy/pasting your code and it works fine using Newtonsoft.Json v6.0.0.0 (from NuGet):
var list = new List<CacheItem<ReadabilitySettings>>();
list.Add(new CacheItem<ReadabilitySettings>() { Key = "key1", Value = new ReadabilitySettings() { FontName = "FontName", FontSize = ReadabilitySettings.FontSizes.Large, IsInverted = false, ReadabilityEnabled = true } });
var json = JsonConvert.SerializeObject(list);
var list2 = JsonConvert.DeserializeObject<List<CacheItem<object>>>(json);
var settings = JsonConvert.DeserializeObject<ReadabilitySettings>(list2.First().Value.ToString());
However, you don't need the last line. Simply switch List<CacheItem<object>> to List<CacheItem<ReadabilitySettings>> in your call to DeserializeObject and it automatically resolves it:
var list2 = JsonConvert.DeserializeObject<List<CacheItem<ReadabilitySettings>>>(json);
Now list2.First().Value.GetType() = ReadabilitySettings and there's no need to do any further deserialising. Is there a reason you're using object?
Edit:
I'm not sure if this helps you necessarily, but given what you're trying to do have you thought about using a custom converter? I had to do this a few days ago for similar reasons. I had an enum property coming back in my JSON (similar to your key) which gave a hint as to what type a property in my JSON and therefore my deserialised class was. The property in my class was an interface rather than an object but the same principle applies for you.
I used a custom converter to automatically handle creating the object of the correct type cleanly.
Here's a CustomConverter for your scenario. If your key is set to readbilitySettings then result.Value is initialised as ReadabilitySettings.
public class CacheItemConverter : CustomCreationConverter<CacheItem<object>>
{
public override CacheItem<object> Create(Type objectType)
{
return new CacheItem<object>();
}
public CacheItem<object> Create(Type objectType, JObject jObject)
{
var keyProperty = jObject.Property("Key");
if (keyProperty == null)
throw new ArgumentException("Key missing.");
var result = new CacheItem<object>();
var keyValue = keyProperty.First.Value<string>();
if (keyValue.Equals("readbilitySettings", StringComparison.InvariantCultureIgnoreCase))
result.Value = new ReadabilitySettings();
else
throw new ArgumentException(string.Format("Unsupported key {0}", keyValue));
return result;
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JObject jObject = JObject.Load(reader);
var target = Create(objectType, jObject);
serializer.Populate(jObject.CreateReader(), target);
/* Here your JSON is deserialised and mapped to your object */
return target;
}
}
Usage:
var list = JsonConvert.DeserializeObject<List<CacheItem<object>>>(json, new CacheItemConverter());
Here's a link to a full working example of it:
http://pastebin.com/PJSvFDsT
Managed to solve it. Sample code did not show that ReadabilitySettings also implemented the INotifyPropertyChanged interface. The subsequent eventhandler wired-up to the PropertyChanged event of ReadabilitySettings somewhere else in the project had some errors and thus the deserializer was not able to instantiate ReadabilitySettings :-).
Not a glamorous save but its working... Thanks for your time.
Related
I've been working on a music game and decided to add convertation of other games' levels. One of the games I decided to convert from uses JSON to store it's levels and so I'm using Newtonsoft.Json for deserializing level data. Level have can 2 object types that are stored in a single array/list with one shared property and one individual property. Keeping that in mind I made level class with it's properties, one base and two inherited classes:
class Chart
{
//Some unimportant properties
//...
public Note[] note;
class Note
{
public int[] beat;
}
class NoteSingle : Note
{
public int x;
}
class NoteRain : Note
{
public int[] endbeat;
}
}
However, when I try deserialize level, note only contains base objects. I tried creating JsonSerializerSettings with TypeNameHandling set to All and passing it to deserialization method, but it didn't worked, note still only have base classes in it.
Basically I need to load level from the file, deserialize it as Chart and make each of the notes in note be one of the types inherited from Note depending on json data. Like if note has x field then load it as NoteSingle and if it has endbeat field then load it as NoteRain.
Repres
class Note
{
public int[] beat;
}
class NoteSingle : Note
{
public int x;
}
class NoteRain : Note
{
public int[] endbeat;
}
class Chart
{
//Some unimportant properties
public Note[] note;
//Some unimportant properties
}
public static void Convert(string path)
{
string rawData = File.ReadAllText(path);
JsonSerializerSettings setts = new JsonSerializerSettings()
{
TypeNameHandling = TypeNameHandling.All
};
Chart ch = JsonConvert.DeserializeObject<Chart>(rawData, setts);
//Level converter code
}
Example data I'm trying to load: https://pastebin.com/zgnRsgWZ
What am I doing wrong?
I try with this code:
var settings = new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.All
};
var chart = new Chart();
chart.note = new Note[]
{
new NoteSingle { x = 37 },
new NoteRain { endbeat = new[] { 9 } }
};
var json = JsonConvert.SerializeObject(chart, settings);
var chart2 = JsonConvert.DeserializeObject<Chart>(json, settings);
And it's working. json has this value:
{
"$type":"Test.Chart, SoApp",
"note":{
"$type":"Test.Chart+Note[], SoApp",
"$values":[
{
"$type":"Test.Chart+NoteSingle, SoApp",
"x":37,
"beat":null
},
{
"$type":"Test.Chart+NoteRain, SoApp",
"endbeat":{
"$type":"System.Int32[], mscorlib",
"$values":[
9
]
},
"beat":null
}
]
}
}
And chart2 has 2 notes of NoteSingle and NoteRain types. Maybe you aren't using TypeNameHandling.All in Serialize. You need to use both on Serialize and Deserialize.
UPDATE
If you haven't control of the generated JSON, you can use a Converter to deserialize it:
public class YourJsonConverter : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var chart = new Chart();
var notes = new List<Note>();
string name = null;
NoteRain noteRain = null;
while (reader.Read())
{
switch (reader.TokenType)
{
case JsonToken.PropertyName:
name = reader.Value.ToString();
break;
case JsonToken.Integer:
if (name == "x")
{
var note = new NoteSingle { x = Convert.ToInt32(reader.Value) };
notes.Add(note);
}
else if (name == "endbeat")
{
if (noteRain == null)
{
noteRain = new NoteRain { endbeat = new[] { Convert.ToInt32(reader.Value) } };
notes.Add(noteRain);
}
else
{
var array = noteRain.endbeat;
noteRain.endbeat = new int[noteRain.endbeat.Length + 1];
for (int i = 0; i < array.Length; i++)
{
noteRain.endbeat[i] = array[i];
}
noteRain.endbeat[noteRain.endbeat.Length - 1] = Convert.ToInt32(reader.Value);
}
}
break;
}
}
chart.note = notes.ToArray();
return chart;
}
public override bool CanWrite => false;
public override bool CanConvert(Type objectType)
{
return true;
}
}
This is a simple example, you must tune it but I think it's easy to do. In the property name I get the name of the property and I use later to create the correct type. If I process a x property I know that is a NoteSingle and I create it and add to notes list.
If, for example, you get a property name like beat and you don't know yet the type of the Note class, use a temporary variable to store and fill and later, when you read a property that you know is from a concrete class, create the Note instance and fill with this variable. And after that, If you read more data of this class, continue filling your instance.
Usage:
var settings = new JsonSerializerSettings();
settings.Converters.Add(new YourJsonConverter());
var chart = JsonConvert.DeserializeObject<Chart>(json, settings);
I have an API that returns a JSON object from a MongoDB in which one of the properties is an "open-ended" document, meaning it can be any valid JSON for that property. I don't know what the names of the properties are ahead of time, they can be any string. I only know that this particular property needs to be serialized exactly how it is stored in the database. So if the property name that was originally stored was "Someproperty", the serialized response in JSON needs to be "Someproperty", NOT "someProperty".
We have this configuration:
ContractResolver = new CamelCasePropertyNamesContractResolver();
in our CustomJsonSerializer, but it is messing with the formatting of the response when returning the "open ended" JSON. It is camel-casing all of these properties when in fact we want the response to be exactly how they are stored in MongoDB (BSON). I know the values are maintaining their case when storing/retrieving via the database, so that is not the issue.
How can I tell JSON.net to essentially bypass the CamelCasePropertyNameResolver for all of the child properties of a particular data point?
EDIT:
Just to give a bit more info, and share what I have already tried:
I thought about overriding the PropertyNameResolver like so:
protected override string ResolvePropertyName(string propertyName)
{
if (propertyName.ToLower().Equals("somedocument"))
{
return propertyName;
}
else return base.ResolvePropertyName(propertyName);
}
However, if I have a JSON structure like this:
{
"Name" : "MyObject",
"DateCreated" : "11/14/2016",
"SomeDocument" :
{
"MyFirstProperty" : "foo",
"mysecondPROPERTY" : "bar",
"another_random_subdoc" :
{
"evenmoredata" : "morestuff"
}
}
}
then I would need all of the properties any child properties' names to remain exactly as is. The above override I posted would (I believe) only ignore on an exact match to "somedocument", and would still camelcase all of the child properties.
What you can do is, for the property in question, create a custom JsonConverter that serializes the property value in question using a different JsonSerializer created with a different contract resolver, like so:
public class AlternateContractResolverConverter : JsonConverter
{
[ThreadStatic]
static Stack<Type> contractResolverTypeStack;
static Stack<Type> ContractResolverTypeStack { get { return contractResolverTypeStack = (contractResolverTypeStack ?? new Stack<Type>()); } }
readonly IContractResolver resolver;
JsonSerializerSettings ExtractAndOverrideSettings(JsonSerializer serializer)
{
var settings = serializer.ExtractSettings();
settings.ContractResolver = resolver;
settings.CheckAdditionalContent = false;
if (settings.PreserveReferencesHandling != PreserveReferencesHandling.None)
{
// Log an error throw an exception?
Debug.WriteLine(string.Format("PreserveReferencesHandling.{0} not supported", serializer.PreserveReferencesHandling));
}
return settings;
}
public AlternateContractResolverConverter(Type resolverType)
{
if (resolverType == null)
throw new ArgumentNullException("resolverType");
resolver = (IContractResolver)Activator.CreateInstance(resolverType);
if (resolver == null)
throw new ArgumentNullException(string.Format("Resolver type {0} not found", resolverType));
}
public override bool CanRead { get { return ContractResolverTypeStack.Count == 0 || ContractResolverTypeStack.Peek() != resolver.GetType(); } }
public override bool CanWrite { get { return ContractResolverTypeStack.Count == 0 || ContractResolverTypeStack.Peek() != resolver.GetType(); } }
public override bool CanConvert(Type objectType)
{
throw new NotImplementedException("This contract resolver is intended to be applied directly with [JsonConverter(typeof(AlternateContractResolverConverter), typeof(SomeContractResolver))] or [JsonProperty(ItemConverterType = typeof(AlternateContractResolverConverter), ItemConverterParameters = ...)]");
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
using (ContractResolverTypeStack.PushUsing(resolver.GetType()))
return JsonSerializer.CreateDefault(ExtractAndOverrideSettings(serializer)).Deserialize(reader, objectType);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
using (ContractResolverTypeStack.PushUsing(resolver.GetType()))
JsonSerializer.CreateDefault(ExtractAndOverrideSettings(serializer)).Serialize(writer, value);
}
}
internal static class JsonSerializerExtensions
{
public static JsonSerializerSettings ExtractSettings(this JsonSerializer serializer)
{
// There is no built-in API to extract the settings from a JsonSerializer back into JsonSerializerSettings,
// so we have to fake it here.
if (serializer == null)
throw new ArgumentNullException("serializer");
var settings = new JsonSerializerSettings
{
CheckAdditionalContent = serializer.CheckAdditionalContent,
ConstructorHandling = serializer.ConstructorHandling,
ContractResolver = serializer.ContractResolver,
Converters = serializer.Converters,
Context = serializer.Context,
Culture = serializer.Culture,
DateFormatHandling = serializer.DateFormatHandling,
DateFormatString = serializer.DateFormatString,
DateParseHandling = serializer.DateParseHandling,
DateTimeZoneHandling = serializer.DateTimeZoneHandling,
DefaultValueHandling = serializer.DefaultValueHandling,
EqualityComparer = serializer.EqualityComparer,
// No Get access to the error event, so it cannot be copied.
// Error = += serializer.Error
FloatFormatHandling = serializer.FloatFormatHandling,
FloatParseHandling = serializer.FloatParseHandling,
Formatting = serializer.Formatting,
MaxDepth = serializer.MaxDepth,
MetadataPropertyHandling = serializer.MetadataPropertyHandling,
MissingMemberHandling = serializer.MissingMemberHandling,
NullValueHandling = serializer.NullValueHandling,
ObjectCreationHandling = serializer.ObjectCreationHandling,
ReferenceLoopHandling = serializer.ReferenceLoopHandling,
// Copying the reference resolver doesn't work in the default case, since the
// actual BidirectionalDictionary<string, object> mappings are held in the
// JsonSerializerInternalBase.
// See https://github.com/JamesNK/Newtonsoft.Json/blob/master/Src/Newtonsoft.Json/Serialization/DefaultReferenceResolver.cs
ReferenceResolverProvider = () => serializer.ReferenceResolver,
PreserveReferencesHandling = serializer.PreserveReferencesHandling,
StringEscapeHandling = serializer.StringEscapeHandling,
TraceWriter = serializer.TraceWriter,
TypeNameHandling = serializer.TypeNameHandling,
// Changes in Json.NET 10.0.1
//TypeNameAssemblyFormat was obsoleted and replaced with TypeNameAssemblyFormatHandling in Json.NET 10.0.1
//TypeNameAssemblyFormat = serializer.TypeNameAssemblyFormat,
TypeNameAssemblyFormatHandling = serializer.TypeNameAssemblyFormatHandling,
//Binder was obsoleted and replaced with SerializationBinder in Json.NET 10.0.1
//Binder = serializer.Binder,
SerializationBinder = serializer.SerializationBinder,
};
return settings;
}
}
public static class StackExtensions
{
public struct PushValue<T> : IDisposable
{
readonly Stack<T> stack;
public PushValue(T value, Stack<T> stack)
{
this.stack = stack;
stack.Push(value);
}
// By using a disposable struct we avoid the overhead of allocating and freeing an instance of a finalizable class.
public void Dispose()
{
if (stack != null)
stack.Pop();
}
}
public static PushValue<T> PushUsing<T>(this Stack<T> stack, T value)
{
if (stack == null)
throw new ArgumentNullException();
return new PushValue<T>(value, stack);
}
}
Then use it like so:
public class RootObject
{
public string Name { get; set; }
public DateTime DateCreated { get; set; }
[JsonProperty(NamingStrategyType = typeof(DefaultNamingStrategy))]
[JsonConverter(typeof(AlternateContractResolverConverter), typeof(DefaultContractResolver))]
public SomeDocument SomeDocument { get; set; }
}
public class SomeDocument
{
public string MyFirstProperty { get; set; }
public string mysecondPROPERTY { get; set; }
public AnotherRandomSubdoc another_random_subdoc { get; set; }
}
public class AnotherRandomSubdoc
{
public string evenmoredata { get; set; }
public DateTime DateCreated { get; set; }
}
(Here I am assuming you want the "SomeDocument" property name to be serialized verbatim, even though it wasn't entirely clear from your question. To do that, I'm using JsonPropertyAttribute.NamingStrategyType from Json.NET 9.0.1. If you're using an earlier version, you'll need to set the property name explicitly.)
Then the resulting JSON will be:
{
"name": "Question 40597532",
"dateCreated": "2016-11-14T05:00:00Z",
"SomeDocument": {
"MyFirstProperty": "my first property",
"mysecondPROPERTY": "my second property",
"another_random_subdoc": {
"evenmoredata": "even more data",
"DateCreated": "2016-11-14T05:00:00Z"
}
}
}
Note that this solution does NOT work well with preserving object references. If you need them to work together, you may need to consider a stack-based approach similar to the one from Json.NET serialize by depth and attribute
Demo fiddle here.
Incidentally, have you considered storing this JSON as a raw string literal, as in the answer to this question?
I think you should look at this backwards.
Instead of trying to NOT touch the properties you don't know, let that be the default behavior and touch the ones you DO know.
In other words, don't use the CamelCasePropertyNamesContractResolver. Deal with the properties you know appropriately and let the other ones pass through transparently.
I know that the same problem is faced by a lot of people in one way or another but what I'm confused about is that how come Newtonsoft JSON Serializer is able to correctly handle this case while JavaScriptSerializer fails to do so.
I'm going to use the same code sample used in one of the other stackoverflow thread (JavascriptSerializer serializing property twice when "new" used in subclass)
void Main()
{
System.Web.Script.Serialization.JavaScriptSerializer serializer = new System.Web.Script.Serialization.JavaScriptSerializer();
var json = serializer.Serialize(new Limited());
Limited status = serializer.Deserialize<Limited>(json); --> throws AmbiguousMatchException
}
public class Full
{
public String Stuff { get { return "Common things"; } }
public FullStatus Status { get; set; }
public Full(bool includestatus)
{
if(includestatus)
Status = new FullStatus();
}
}
public class Limited : Full
{
public new LimitedStatus Status { get; set; }
public Limited() : base(false)
{
Status = new LimitedStatus();
}
}
public class FullStatus
{
public String Text { get { return "Loads and loads and loads of things"; } }
}
public class LimitedStatus
{
public String Text { get { return "A few things"; } }
}
But if I use Newtonsoft Json Serializer, everythings works fine. Why? And is it possible to achieve the same using JavaScriptSerializer?
void Main()
{
var json = JsonConvert.SerializeObject(new Limited());
Limited status = JsonConvert.DeserializeObject<Limited>(json); ----> Works fine.
}
The reason this works in Json.NET is that it has specific code to handle this situation. From JsonPropertyCollection.cs:
/// <summary>
/// Adds a <see cref="JsonProperty"/> object.
/// </summary>
/// <param name="property">The property to add to the collection.</param>
public void AddProperty(JsonProperty property)
{
if (Contains(property.PropertyName))
{
// don't overwrite existing property with ignored property
if (property.Ignored)
return;
JsonProperty existingProperty = this[property.PropertyName];
bool duplicateProperty = true;
if (existingProperty.Ignored)
{
// remove ignored property so it can be replaced in collection
Remove(existingProperty);
duplicateProperty = false;
}
else
{
if (property.DeclaringType != null && existingProperty.DeclaringType != null)
{
if (property.DeclaringType.IsSubclassOf(existingProperty.DeclaringType))
{
// current property is on a derived class and hides the existing
Remove(existingProperty);
duplicateProperty = false;
}
if (existingProperty.DeclaringType.IsSubclassOf(property.DeclaringType))
{
// current property is hidden by the existing so don't add it
return;
}
}
}
if (duplicateProperty)
throw new JsonSerializationException("A member with the name '{0}' already exists on '{1}'. Use the JsonPropertyAttribute to specify another name.".FormatWith(CultureInfo.InvariantCulture, property.PropertyName, _type));
}
Add(property);
}
As you can see above, there is specific code here to prefer derived class properties over base class properties of the same name and visibility.
JavaScriptSerializer has no such logic. It simply calls Type.GetProperty(string, flags)
PropertyInfo propInfo = serverType.GetProperty(memberName,
BindingFlags.Instance | BindingFlags.IgnoreCase | BindingFlags.Public);
This method is documented to throw an exception in exactly this situation:
Situations in which AmbiguousMatchException occurs include the following:
A type contains two indexed properties that have the same name but different numbers of parameters. To resolve the ambiguity, use an overload of the GetProperty method that specifies parameter types.
A derived type declares a property that hides an inherited property with the same name, using the new modifier (Shadows in Visual Basic). To resolve the ambiguity, include BindingFlags.DeclaredOnly to restrict the search to members that are not inherited.
I don't know why Microsoft didn't add logic for this to JavaScriptSerializer. It's really a very simple piece of code; perhaps it got eclipsed by DataContractJsonSerializer?
You do have a workaround, which is to write a custom JavaScriptConverter:
public class LimitedConverter : JavaScriptConverter
{
const string StuffName = "Stuff";
const string StatusName = "Status";
public override object Deserialize(IDictionary<string, object> dictionary, Type type, JavaScriptSerializer serializer)
{
var limited = new Limited();
object value;
if (dictionary.TryGetValue(StuffName, out value))
{
// limited.Stuff = serializer.ConvertToType<string>(value); // Actually it's get only.
}
if (dictionary.TryGetValue(StatusName, out value))
{
limited.Status = serializer.ConvertToType<LimitedStatus>(value);
}
return limited;
}
public override IDictionary<string, object> Serialize(object obj, JavaScriptSerializer serializer)
{
var limited = (Limited)obj;
if (limited == null)
return null;
var dict = new Dictionary<string, object>();
if (limited.Stuff != null)
dict.Add(StuffName, limited.Stuff);
if (limited.Status != null)
dict.Add(StatusName, limited.Status);
return dict;
}
public override IEnumerable<Type> SupportedTypes
{
get { return new [] { typeof(Limited) } ; }
}
}
And then use it like:
try
{
System.Web.Script.Serialization.JavaScriptSerializer serializer = new System.Web.Script.Serialization.JavaScriptSerializer();
serializer.RegisterConverters(new JavaScriptConverter[] { new LimitedConverter() });
var json = serializer.Serialize(new Limited());
Debug.WriteLine(json);
var status = serializer.Deserialize<Limited>(json);
var json2 = serializer.Serialize(status);
Debug.WriteLine(json2);
}
catch (Exception ex)
{
Debug.Assert(false, ex.ToString()); // NO ASSERT.
}
I'm parsing a JSON string using the NewtonSoft JObject.
How can I get values from a dynamic object programmatically?
I want to simplify the code to not repeat myself for every object.
public ExampleObject GetExampleObject(string jsonString)
{
ExampleObject returnObject = new ExampleObject();
dynamic dynamicResult = JObject.Parse(jsonString);
if (!ReferenceEquals(dynamicResult.album, null))
{
//code block to extract to another method if possible
returnObject.Id = dynamicResult.album.id;
returnObject.Name = dynamicResult.album.name;
returnObject.Description = dynamicResult.albumsdescription;
//etc..
}
else if(!ReferenceEquals(dynamicResult.photo, null))
{
//duplicated here
returnObject.Id = dynamicResult.photo.id;
returnObject.Name = dynamicResult.photo.name;
returnObject.Description = dynamicResult.photo.description;
//etc..
}
else if..
//etc..
return returnObject;
}
Is there any way I can extract the code blocks in the "if" statements to a separate method e.g:
private void ExampleObject GetExampleObject([string of desired type goes here? album/photo/etc])
{
ExampleObject returnObject = new ExampleObject();
returnObject.Id = dynamicResult.[something goes here?].id;
returnObject.Name = dynamicResult.[something goes here?].name;
//etc..
return returnObject;
}
Is it even possible since we can't use reflection for dynamic objects. Or am I even using the JObject correctly?
Thanks.
Assuming you're using the Newtonsoft.Json.Linq.JObject, you don't need to use dynamic. The JObject class can take a string indexer, just like a dictionary:
JObject myResult = GetMyResult();
returnObject.Id = myResult["string here"]["id"];
Hope this helps!
Another way of targeting this is by using SelectToken (Assuming that you're using Newtonsoft.Json):
JObject json = GetResponse();
var name = json.SelectToken("items[0].name");
For a full documentation: https://www.newtonsoft.com/json/help/html/SelectToken.htm
with dynamic keyword like below:
private static JsonSerializerSettings jsonSettings;
private static T Deserialize<T>(string jsonData)
{
return JsonConvert.DeserializeObject<T>(jsonData, jsonSettings);
}
//if you know what will return
var jresponse = Deserialize<SearchedData>(testJsonString);
//if you know return object type you should sign it with json attributes like
[JsonObject(MemberSerialization = MemberSerialization.OptIn)]
public class SearchedData
{
[JsonProperty(PropertyName = "Currency")]
public string Currency { get; set; }
[JsonProperty(PropertyName = "Routes")]
public List<List<Route>> Routes { get; set; }
}
// if you don't know the return type use dynamic as generic type
var jresponse = Deserialize<dynamic>(testJsonString);
I have this (simplified) class:
public class StarBuildParams
{
public int BaseNo { get; set; }
public int Width { get; set; }
}
And I have to transform instances of it to a querystring like this:
"BaseNo=5&Width=100"
Additionally I have to transform such a querystring back in an object of that class.
I know that this is pretty much what a modelbinder does, but I don't have the controller context in my situation (some deep buried class running in a thread).
So, is there a simple way to convert a object in a query string and back without having a controller context?
It would be great to use the modelbinding but I don't know how.
A solution with Newtonsoft Json serializer and linq:
string responseString = "BaseNo=5&Width=100";
var dict = HttpUtility.ParseQueryString(responseString);
string json = JsonConvert.SerializeObject(dict.Cast<string>().ToDictionary(k => k, v => dict[v]));
StarBuildParams respObj = JsonConvert.DeserializeObject<StarBuildParams>(json);
You can use reflection, something like this:
public T GetFromQueryString<T>() where T : new(){
var obj = new T();
var properties = typeof(T).GetProperties();
foreach(var property in properties){
var valueAsString = HttpContext.Current.Request.QueryString[property.PropertyName];
var value = Parse( valueAsString, property.PropertyType);
if(value == null)
continue;
property.SetValue(obj, value, null);
}
return obj;
}
You'll need to implement the Parse method, just using int.Parse, decimal.Parse, DateTime.Parse, etc.
Use this Parse method with the ivowiblo's solution (accepted answer):
public object Parse(string valueToConvert, Type dataType)
{
TypeConverter obj = TypeDescriptor.GetConverter(dataType);
object value = obj.ConvertFromString(null, CultureInfo.InvariantCulture, valueToConvert);
return value;
}
You can set the properties of this object in its constructor by retrieving the relevant values from the querystring
public StarBuildParams()
{
this.BaseNo = Int32.Parse(Request.QueryString["BaseNo"].ToString());
this.Width = Int32.Parse(Request.QueryString["Width"].ToString());
}
and you can ensure that the object is converted to the correct querystring format by overriding the ToString method.
public override string ToString()
{
return String.Format("BaseNo={0}&Width={1}", this.BaseNo, this.Width);
}
You'll still need to construct and call ToString in the appropriate places, but this should help.
You can just use .NET's HttpUtility.ParseQueryString() method:
HttpUtility.ParseQueryString("a=b&c=d") produces a NameValueCollection as such:
[0] Key = "a", Value = "b"
[1] Key = "c", Value = "d"
This should work so long as none of the properties match any other route parameters like controller, action, id, etc.
new RouteValueDictionary(Model)
http://msdn.microsoft.com/en-us/library/cc680272.aspx
Initializes a new instance of the RouteValueDictionary class and adds
values that are based on properties from the specified object.
To parse back from the query string you can use the model class as an action parameter and let the ModelBinder do it's job.
Serialize query string and deserialize to your class object
JObject json;
Request.RequestUri.TryReadQueryAsJson(out json);
string sjson = JsonConvert.SerializeObject(json);
StarBuildParams query = JsonConvert.DeserializeObject<StarBuildParams>(sjson);
Building off of Ivo and Anupam Singh's great solutions above, here is the code that I used to turn this into a base class for POST requests (in the event that you may only have the raw query string like in a Web API setup). This code works for lists of objects, but could easily be modified to parse a single object.
public class PostOBjectBase
{
/// <summary>
/// Returns a List of List<string> - one for each object that is going to be parsed.
/// </summary>
/// <param name="entryListString">Raw query string</param>
/// <param name="firstPropertyNameOfObjectToParseTo">The first property name of the object that is sent in the list (unless otherwise specified). Used as a key to start a new object string list. Ex: "id", etc.</param>
/// <returns></returns>
public List<List<string>> GetQueryObjectsAsStringLists(string entryListString, string firstPropertyNameOfObjectToParseTo = null)
{
// Decode the query string (if necessary)
string raw = System.Net.WebUtility.UrlDecode(entryListString);
// Split the raw query string into it's data types and values
string[] entriesRaw = raw.Split('&');
// Set the first property name if it is not provided
if (firstPropertyNameOfObjectToParseTo == null)
firstPropertyNameOfObjectToParseTo = entriesRaw[0].Split("=").First();
// Create a list from the raw query array (more easily manipulable) for me at least
List<string> rawList = new List<string>(entriesRaw);
// Initialize List of string lists to return - one list = one object
List<List<string>> entriesList = new List<List<string>>();
// Initialize List for current item to be added to in foreach loop
bool isFirstItem = false;
List<string> currentItem = new List<string>();
// Iterate through each item keying off of the firstPropertyName of the object we will ultimately parse to
foreach (string entry in rawList)
{
if (entry.Contains(firstPropertyNameOfObjectToParseTo + "="))
{
// The first item needs to be noted in the beginning and not added to the list since it is not complete
if (isFirstItem == false)
{
isFirstItem = true;
}
// Finished getting the first object - we're on the next ones in the list
else
{
entriesList.Add(currentItem);
currentItem = new List<string>();
}
}
currentItem.Add(entry);
}
// Add the last current item since we could not in the foreach loop
entriesList.Add(currentItem);
return entriesList;
}
public T GetFromQueryString<T>(List<string> queryObject) where T : new()
{
var obj = new T();
var properties = typeof(T).GetProperties();
foreach (string entry in queryObject)
{
string[] entryData = entry.Split("=");
foreach (var property in properties)
{
if (entryData[0].Contains(property.Name))
{
var value = Parse(entryData[1], property.PropertyType);
if (value == null)
continue;
property.SetValue(obj, value, null);
}
}
}
return obj;
}
public object Parse(string valueToConvert, Type dataType)
{
if (valueToConvert == "undefined" || valueToConvert == "null")
valueToConvert = null;
TypeConverter obj = TypeDescriptor.GetConverter(dataType);
object value = obj.ConvertFromString(null, CultureInfo.InvariantCulture, valueToConvert);
return value;
}
}
Then you can inherit from this class in wrapper classes for POST requests and parse to whichever objects you need. In this case, the code parses a list of objects passed as a query string to a list of wrapper class objects.
For example:
public class SampleWrapperClass : PostOBjectBase
{
public string rawQueryString { get; set; }
public List<ObjectToParseTo> entryList
{
get
{
List<List<string>> entriesList = GetQueryObjectsAsStringLists(rawQueryString);
List<ObjectToParseTo> entriesFormatted = new List<ObjectToParseTo>();
foreach (List<string> currentObject in entriesList)
{
ObjectToParseToentryPost = GetFromQueryString<ObjectToParseTo>(currentObject);
entriesFormatted.Add(entryPost);
}
return entriesFormatted;
}
}
}