I have been trying to solve this problem for about 2 days and I have no idea what I should do next. On both PC of this question used a 64-bit system (Windows 10 on first PC, Windows Server 2012 on second PC). Project built with .NET 5.
The details:
I have simple anonymous object, that's need to be serialized:
object s = new
{
repositories = new
{
path= new
{
basePath= new
{
urlName = "https://example.com"
}
}
},
variables = new
{
alpha = new { value = "wagh89boa" },
gamma = new { value = "f6q234v0go" },
}
};
I am using Newtonsoft.Json for object serialization. On the first PC i get the expected result which is correct:
JsonConvert.SerializeObject(s);
//{"repositories":{"path":{"basePath":{"urlName":"https://example.com"}}},"variables":{"alpha":{"value":"wagh89boa"},"gamma":{"value":"f6q234v0go"}}}
But when i run the same assembly on second PC (strictly speaking my app is running as Windows-service), I get wrong JSON string (2 braces after "https://example.com" insted of 3):
JsonConvert.SerializeObject(s);
//{"repositories":{"path":{"basePath":{"urlName":"https://example.com"}},"variables":{"alpha":{"value":"wagh89boa"},"gamma":{"value":"f6q234v0go"}}}
What i tried to do:
using System.Text.Json for serialization;
create a separate model-class for each entity;
create heir of JsonConverter with manually JSON creation;
None of these helped me solve the problem.
I would like to highlight the third point: I logged each step of the JsonConverter.WriteJson() (because I can't deploy the development environment on the second PC) method and found some strangeness: at the moment when I "closed the braces" after the "https://example.com", the first two braces closed as expected, but the third never set on the 2-nd PC (on the 1-st PC converter works as expected). Here is the code of my converter (without logging):
public class MyConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return true;
}
public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer)
{
if (value == null)
{
return;
}
//writer.AutoCompleteOnClose = false; //when using writer.WriteRaw("}");
var properties = value.GetType().GetProperties();
writer.WriteStartObject();
foreach ( var property in properties )
{
writer.WritePropertyName(property.Name);
if (property.PropertyType == typeof(string))
{
writer.WriteValue(property.GetValue(value));
}
else
{
serializer.Serialize(writer, property.GetValue(value));
}
}
//none of this methods doesn't insert third brace after "https://example.com" on second PC and I absolutely don't understand why....
writer.WriteEndObject();
//writer.WriteEnd();
//writer.WriteRaw("}");
}
}
Any ideas?
Related
I'm working with a third-party API where you send an order along with an array of options you want to add to the order. Each Option has an OptionId.
You'd assume the required JSON would look something like this:
{
//More properties cut for brevity
"options":[
{
"optionId":"ID 1"
},
{
"optionId":"ID 2"
}
]
}
Instead, the required JSON actually looks like this:
{
//More properties cut for brevity
"options":[
{
"optionId":"ID 1",
"optionId":"ID 2"
}
]
}
Is there any way I can represent that data structure in C#? And if yes, is there any way I can tell Json.NET to serialize it the way the API requires?
The desired JSON is technically valid, though not recommended because having duplicate keys in an object makes it much more difficult to work with. I understand this is a third party API, and you don't have any other choice, but I want to make clear to future readers that this is a bad design.
To represent the option IDs in C#, you can use a simple List<string>. You will need to make a custom JsonConverter to write out the desired structure to the JSON. We'll call it OptionListConverter. So declare the option list as shown below:
class Order
{
[JsonProperty("options")]
[JsonConverter(typeof(OptionListConverter))]
public List<string> OptionIds { get; set; }
}
Then define the OptionListConverter like this:
class OptionListConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(List<string>);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var list = (List<string>)value;
writer.WriteStartArray();
writer.WriteStartObject();
foreach (var id in list)
{
writer.WritePropertyName("optionId");
writer.WriteValue(id);
}
writer.WriteEndObject();
writer.WriteEndArray();
}
public override bool CanRead
{
get { return false; }
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
You can then create the desired JSON like this:
var order = new Order
{
OptionIds = new List<string> { "ID 1", "ID 2" }
};
var json = JsonConvert.SerializeObject(order, Formatting.Indented);
Working demo: https://dotnetfiddle.net/rEYl8p
I am working on a C# project.
[
{"FirstName":"XYZ","LastName":"SSS"},
{"FirstName":"ABC","LastName":"NNN"}
]
Each row represents an object of class ChildDTO.
I read the above data from the file and am trying to deserialize into a ParentCollection object like below:
string file = System.IO.File.ReadAllText("Children_CA.txt");
ParentCollection pCollection = Newtonsoft.Json.JsonConvert.DeserializeObject<ParentCollection>(file);
My DTO classes are like below:
[Serializable]
public class ParentCollection : CollectionBase
{
public void Add(ChildDTO dto)
{
//List is from Systems.Collections.CollectionBase class
List.Add(dto);
}
}
[Serializable]
public class ChildDTO
{
// properties like FirstName and LastName goes here
}
I cannot change my DTO classes since they are old and already in production from the past 20 years and many applications are using them.
When I see Quick Watch on pCollection, I notice the collection is having objects of type Newtonsoft.Json.Linq.JObject. I am hoping to have objects of type ChildDTO
Please let me know what mistake I am doing.
The problem is that CollectionBase, and by extension your ParentCollection class, are not generic, so Json.Net doesn't know that the items in the collection are supposed to be ChildDTO objects. Since it doesn't know what type the items are, it just uses JObject instead. You can fix the problem using a custom JsonConverter class like this:
class ParentCollectionConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(ParentCollection);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
ParentCollection pc = new ParentCollection();
JArray array = JArray.Load(reader);
foreach (var item in array)
{
pc.Add(item.ToObject<ChildDTO>());
}
return pc;
}
public override bool CanWrite
{
get { return false; }
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
To use the converter, pass it to DeserializeObject<T> like this:
ParentCollection pCollection =
JsonConvert.DeserializeObject<ParentCollection>(json, new ParentCollectionConverter());
Working demo here: https://dotnetfiddle.net/3GM22c
Good morning. If you need the ChildDto's list, please convert it to List. Please refer to the code and image below. Many thanks.
string file = System.IO.File.ReadAllText("Children_CA.txt");
var results1 = JsonConvert.DeserializeObject<List<ChildDTO>>(file);
Temporary note: This is NOT a duplicate of the above mentioned post
Let's say I have a server-side class structure like this.
public class Test
{
// this can be any kind of "Tag"
public object Data { get; set; }
}
public class Other
{
public string Test { get; set; }
}
Now a string like this is coming from let's say the client.
{"Data": [{$type: "MyProject.Other, MyProject", "Test": "Test"}] }
When I try to deserialize this into a Test instance, I get a result where the Tag property is a JToken instead of some kind of collection, for example ArrayList or List<object>.
I understand that Json.NET cannot deserialize into a strongly typed list, but I'd expect that it respects that it's at least a list.
Here is my current deserialization code.
var settings = new JsonSerializerSettings()
{
TypeNameHandling = TypeNameHandling.Auto,
};
var str = "{\"Data\": [{\"$type\": \"MyProject.Other, MyProject\", \"Test\": \"Test\"}] }";
var test = JsonConvert.Deserialize<Test>(str, settings);
// this first assertion fails
(test.Data is IList).ShouldBeTrue();
(((IList)test.Data)[0] is Other).ShouldBeTrue();
I'm aware of the fact that if I serialize such a structure, then by default I'll get a { $type: ..., $values: [...]} structure in the JSON string instead of a pure array literal, and that will indeed properly deserialize. However, the client is sending a pure array literal, so I should be able to handle that in some way.
I managed to put together a JsonConverter to handle these kind of untyped lists. The converter applies when the target type is object. Then if the current token type is array start ([) it will force a deserialization into List<object>. In any other case it will fall back to normal deserialization.
This is a first version which passes my most important unit tests, however as I'm not a Json.NET expert, it might break some things unexpectedly. Please if anyone sees anything what I didn't, leave a comment.
public class UntypedListJsonConverter : JsonConverter
{
public override bool CanWrite => false;
public override bool CanRead => true;
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotSupportedException();
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType != JsonToken.StartArray)
{
return serializer.Deserialize(reader);
}
return serializer.Deserialize<List<object>>(reader);
}
public override bool CanConvert(Type objectType)
{
return objectType == typeof(object);
}
}
Usage example:
var settings = new JsonSerializerSettings()
{
TypeNameHandling = TypeNameHandling.Auto,
Converters = new[] { new UntypedListJsonConverter() }
};
var str = "{\"Data\": [{\"$type\": \"MyProject.Other, MyProject\", \"Test\": \"Test\"}] }";
var test = JsonConvert.Deserialize<Test>(str, settings);
// now these assertions pass
(test.Data is IList).ShouldBeTrue();
(((IList)test.Data)[0] is Other).ShouldBeTrue();
Try this:
public class Test
{
public Dictionary<string, List<Other>> Data { get; } = new Dictionary<string, List<Other>>();
}
You need to set up the class you are trying to fill from json data to match as closely to the json structure. From the looks of it, the json looks a dictionary where the keys are strings and the values are arrays of Other objects.
Attempting to serialize DrivInfo to a Json string with this code only return the "name" property:
DriveInfo dr = new DriveInfo("C");
string json = Newtonsoft.Json.JsonConvert.SerializeObject(dr);
The string result is only:
{"_name":"C:\"}
DrivInfo is sealed so I cannot change anything. Is there a way to do it excluding wrapping?
Your difficulty is that DriveInfo implements the ISerializable interface for custom serialization, and Json.NET respects this interface by default, using it to serialize and deserialize the type. And since DriveInfo is defined entirely by the name of the drive, that's all that it's custom serialization code stores into the serialization stream.
Since you just want to dump the properties of DriveInfo and do not care about deserialization, you can disable use of ISerializable by setting DefaultContractResolver.IgnoreSerializableInterface = true. However, if you do this, you'll get an infinite recursion serializing dr.RootDirectory.Root.Root.... To work around this, you could create a JsonConverter for DirectoryInfo:
public class DirectoryInfoConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(DirectoryInfo);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null)
return null;
var token = JToken.Load(reader);
return new DirectoryInfo((string)token);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
writer.WriteValue(value.ToString());
}
}
Then you could do:
DriveInfo dr = new DriveInfo("C");
var settings = new JsonSerializerSettings
{
ContractResolver = new DefaultContractResolver { IgnoreSerializableInterface = true },
Converters = new [] { new DirectoryInfoConverter() },
};
var json = Newtonsoft.Json.JsonConvert.SerializeObject(dr, Formatting.Indented, settings);
But at this point, it's probably easier just to serialize an intermediate anonymous type:
var json = JsonConvert.SerializeObject(
new
{
Name = dr.Name,
DriveType = dr.DriveType,
DriveFormat = dr.DriveFormat,
IsReady = dr.IsReady,
AvailableFreeSpace = dr.AvailableFreeSpace,
TotalFreeSpace = dr.TotalFreeSpace,
TotalSize = dr.TotalSize,
RootDirectory = dr.RootDirectory.ToString(),
VolumeLabel = dr.VolumeLabel
},
Formatting.Indented);
(Of course, you won't be able to deserialize it in this format.)
The class contains its own custom serialization which specifies that only the _name field should be included. The other properties aren't stored in the class. They're determined by the environment, so they can't be replaced with deserialized values.
From the source code:
private const String NameField = "_name"; // For serialization
void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)
{
// No need for an additional security check - everything is public.
info.AddValue(NameField, _name, typeof(String));
}
Other properties are determined by actually inspecting the drive referenced by the DriveInfo. Properties like TotalFreeSpace and TotalFreeSize can change from moment to moment and may not (probably don't) apply on another computer where the class could be deserialized.
If you could serialize the whole thing and deserialize it someplace else, then it would be possible to create a deserialized instance of the class where all of the property values are wrong, because, for example, they actually describe the c: drive on another computer. But the purpose of the class is to return information about a drive on the computer where the code is executing.
If you want to pass that data around then you can always create your own class and serialize that.
This question already has answers here:
How to handle both a single item and an array for the same property using JSON.net
(9 answers)
Closed 8 years ago.
I'm trying to connect with an external API. It gives me the results in JSON and depending my search term, it could give zero, one or more-than-one results.
I have mapped this to a C# class as follows:
Class Item
- List<ItemResult> Results { get; set; }
- Other properties, such as Name or Description (strings)
Whenever the JSON has an array of ItemResult (marked with []), it works fine, when the JSON has a single ItemResult (marked with {}), the parsing gives an error, because it expects a collection or array.
My request is done as follows:
private T DoRequest<T>(string url)
{
try
{
var webRequest = (HttpWebRequest) WebRequest.Create(url);
webRequest.Method = "GET";
webRequest.Accept = "application/json";
webRequest.UserAgent = UserAgent;
var webResponse = webRequest.GetResponse();
using (var streamReader = new StreamReader(webResponse.GetResponseStream()))
{
var responseResult = streamReader.ReadToEnd();
return JsonConvert.DeserializeObject<T>(CorrectJson(responseResult));
}
}
catch (Exception e)
{
UtilityBL.LogError(e.Message);
return default(T);
}
}
My question is: how can I make sure that this code handles both JSON results with an array of ItemResults as well as a single ItemResult?
Perhaps I need to manually adjust the JSON result?
Thanks!
Edit:
it's similar to this question, but instead of JQuery or Javascript, I need a .NET solution:
How to read both single object & array of objects in json using javascript/jquery
I have also tried following code, but it fails since JsonConvert tells me there now are 2 Album matches:
public class Albummatches
{
[JsonProperty("Album")]
public List<Album> Albums { get; set; }
[JsonProperty("Album")]
public Album Album {
set
{
Albums = new List<Album>();
Albums.Add(value);
}
}
}
I think I got it!
Here's what I did:
Create a new JsonConverter:
This will make sure that I will always get a list.
public class JsonAlbumConverter : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.StartArray)
return serializer.Deserialize<List<Album>>(reader);
var itm = serializer.Deserialize<Album>(reader);
return new List<Album> {itm};
}
public override bool CanConvert(Type objectType)
{
throw new NotImplementedException();
}
}
Force the use of my converter by applying an attribute to the property
public class Albummatches
{
[JsonProperty("Album")]
[JsonConverter(typeof(JsonAlbumConverter))]
public List<Album> Albums { get; set; }
}
It works but I was really hoping for a built-in solution, since I suspect this being a frequent issue...
Make your single result a member with a set method, and override the set method to create a list and add the 1 item to it.
That handles your single and multiple results, and your code that uses the result only has to handle the multiple results.
For your example (this is psudeo code, I don't know the exact syntax)
Class Item
- List<ItemResult> Results { get; set; }
- ItemResult Result { set;}
Override the setter for the single result like:
setResult(ItemResult r){
Results = new List<ItemResult>();
Results.add(r);
}