How to handle json that returns both a string and a string array?
I'll use Json.Net. The idea is: "declare position
as a List<string>
and if the value in json is a string. then convert it to a List"
Code to deserialize
var api = JsonConvert.DeserializeObject<SportsAPI>(json);
JsonConverter
public class StringConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
throw new NotImplementedException();
}
public override object ReadJson(Newtonsoft.Json.JsonReader reader, Type objectType, object existingValue, Newtonsoft.Json.JsonSerializer serializer)
{
if(reader.ValueType==typeof(string))
{
return new List<string>() { (string)reader.Value };
}
return serializer.Deserialize<List<string>>(reader);
}
public override void WriteJson(Newtonsoft.Json.JsonWriter writer, object value, Newtonsoft.Json.JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
Sample Json
{
"player": [
{
"eligible_positions": {
"position": "QB"
}
},
{
"eligible_positions": {
"position": [
"WR",
"W/R/T"
]
}
}
]
}
Classes (Simplified version)
public class EligiblePositions
{
[JsonConverter(typeof(StringConverter))] // <-- See This
public List<string> position { get; set; }
}
public class Player
{
public EligiblePositions eligible_positions { get; set; }
}
public class SportsAPI
{
public List<Player> player { get; set; }
}
Parse Json to return string or string array?
Rather than returning one of two possible return types, it's probably simpler to just coerce a single string to an array containing that string. Assuming you don't control the Json, you probably want to write a custom JsonConverter
. Here's my quick and dirty stab at the problem:
public class LangEntryConverter: JsonConverter<string[]>
{
// WriteJson implementation only needed if you need to serialize a value back to Json
public override void WriteJson(JsonWriter writer, string[] value, JsonSerializer serializer)
{
if (value.Length == 1)
{
writer.WriteValue(value[0]);
}
else
{
writer.WriteStartArray();
for (var i = 0; i < value.Length; i++)
{
writer.WriteValue(value[i]);
}
writer.WriteEndArray();
}
}
public override string[] ReadJson(JsonReader reader, Type objectType, string[] existingValue, bool hasExistingValue, JsonSerializer serializer)
{
var values = new List<string>();
if (reader.TokenType == JsonToken.StartArray)
{
while (reader.Read() && reader.TokenType != JsonToken.EndArray)
{
if (reader.TokenType == JsonToken.String)
{
values.Add((string)reader.Value);
}
else
{
throw new Exception($"Unexpected token type: {reader.TokenType}");
}
}
}
else if (reader.TokenType == JsonToken.String)
{
values.Add((string)reader.Value);
}
return values.ToArray();
}
}
And then call it like so:
// Note: double-quotations are required for C#'s verbatim string syntax; they are not part of the Json
var json = @"{
""foo"": ""one"",
""bar"": [""one"", ""two"", ""three""]
}";
var lang = JsonConvert.DeserializeObject<Dictionary<string, string[]>>(json, new LangEntryConverter());
Console.WriteLine(String.Join(", ", lang["foo"])); // one
Console.WriteLine(String.Join(", ", lang["bar"])); // one, two, three
Of course, in your particular situation, this may require some tweaking.
How to handle both a single item and an array for the same property using JSON.net
The best way to handle this situation is to use a custom JsonConverter
.
Before we get to the converter, we'll need to define a class to deserialize the data into. For the Categories
property that can vary between a single item and an array, define it as a List<string>
and mark it with a [JsonConverter]
attribute so that JSON.Net will know to use the custom converter for that property. I would also recommend using [JsonProperty]
attributes so that the member properties can be given meaningful names independent of what is defined in the JSON.
class Item
{
[JsonProperty("email")]
public string Email { get; set; }
[JsonProperty("timestamp")]
public int Timestamp { get; set; }
[JsonProperty("event")]
public string Event { get; set; }
[JsonProperty("category")]
[JsonConverter(typeof(SingleOrArrayConverter<string>))]
public List<string> Categories { get; set; }
}
Here is how I would implement the converter. Notice I've made the converter generic so that it can be used with strings or other types of objects as needed.
class SingleOrArrayConverter<T> : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return (objectType == typeof(List<T>));
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JToken token = JToken.Load(reader);
if (token.Type == JTokenType.Array)
{
return token.ToObject<List<T>>();
}
return new List<T> { token.ToObject<T>() };
}
public override bool CanWrite
{
get { return false; }
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
Here is an short program demonstrating the converter in action with your sample data:
class Program
{
static void Main(string[] args)
{
string json = @"
[
{
""email"": ""john.doe@sendgrid.com"",
""timestamp"": 1337966815,
""category"": [
""newuser"",
""transactional""
],
""event"": ""open""
},
{
""email"": ""jane.doe@sendgrid.com"",
""timestamp"": 1337966815,
""category"": ""olduser"",
""event"": ""open""
}
]";
List<Item> list = JsonConvert.DeserializeObject<List<Item>>(json);
foreach (Item obj in list)
{
Console.WriteLine("email: " + obj.Email);
Console.WriteLine("timestamp: " + obj.Timestamp);
Console.WriteLine("event: " + obj.Event);
Console.WriteLine("categories: " + string.Join(", ", obj.Categories));
Console.WriteLine();
}
}
}
And finally, here is the output of the above:
email: john.doe@sendgrid.com
timestamp: 1337966815
event: open
categories: newuser, transactional
email: jane.doe@sendgrid.com
timestamp: 1337966815
event: open
categories: olduser
Fiddle: https://dotnetfiddle.net/lERrmu
EDIT
If you need to go the other way, i.e. serialize, while keeping the same format, you can implement the WriteJson()
method of the converter as shown below. (Be sure to remove the CanWrite
override or change it to return true
, or else WriteJson()
will never be called.)
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
List<T> list = (List<T>)value;
if (list.Count == 1)
{
value = list[0];
}
serializer.Serialize(writer, value);
}
Fiddle: https://dotnetfiddle.net/XG3eRy
How do you represent a JSON array of strings?
I'll elaborate a bit more on ChrisR awesome answer and bring images from his awesome reference.
A valid JSON always starts with either curly braces {
or square brackets [
, nothing else.
{
will start an object
:
{ "key": value, "another key": value }
Hint: although javascript accepts single quotes
'
, JSON only takes double ones"
.
[
will start an array
:
[value, value]
Hint: spaces among elements are always ignored by any JSON parser.
And value
is an object
, array
, string
, number
, bool
or null
:
So yeah, ["a", "b"]
is a perfectly valid JSON, like you could try on the link Manish pointed.
Here are a few extra valid JSON examples, one per block:
{}
[0]
{"__comment": "json doesn't accept comments and you should not be commenting even in this way", "avoid!": "also, never add more than one key per line, like this"}
[{ "why":null} ]
{
"not true": [0, false],
"true": true,
"not null": [0, 1, false, true, {
"obj": null
}, "a string"]
}
How can I parse JSON Array to string?
First of all, your JSON string has an invalid format. You can check it here to validate. Secondly, the best way to do this is to create a class and than use JsonConvert.DeserializeObject
. On your case, here is the full working solution:
static void Main(string[] args)
{
string json = @"{'family': [{'fatherFirstName': 'John', 'motherFirstName': 'July'}, {'fatherFirstName': 'Jack', 'motherFirstName': 'Monika'}]}";
Families families = JsonConvert.DeserializeObject<Families>(json);
foreach (var family in families.family)
Console.WriteLine(family.fatherFirstName);
}
public class Families
{
public List<Family> family { get; set; }
}
public class Family
{
public string fatherFirstName { get; set; }
public string motherFirstName { get; set; }
}
How to parse array of strings in swift?
If you're getting the 3840 error, then the JSON is not valid JSON, period.
The JSON string equivalent of the array is supposed to be
let jsonString = "[\"one\",\"two\",\"three\"]"
The Swift 4 literal multiline syntax shows the actual format without the escaping backslashes
let jsonString = """
["one","two","three"]
"""
You are able to parse it without any options (no .allowFragments, and no .mutableContainers)
let data = Data(jsonString.utf8)
do {
let array = try JSONSerialization.jsonObject(with: data) as! [String]
print(array) // ["one", "two", "three"]
} catch {
print(error)
}
Almost everybody misuses the JSONSerialization Reading Options
- .allowFragments is only needed if the root object is not array and not dictionary
- .mutableContainers is completely meaningless in Swift
In 99% of the cases you can omit the options
parameter
Parse a string array in json response
If you just need to parse the JSON and iterate over the strings in the fields
array, you can do that like this:
JObject obj = JObject.Parse(json);
foreach (var path in obj["fields"].Values<string>())
{
Console.WriteLine(path);
}
If you need to break down the strings into name-value pairs, you can split on /
and loop over the parts like this (inside the previous loop):
string[] parts = path.Split('/');
for (var i = 1; i < parts.Length; i += 2)
{
var name = parts[i];
var value = (i + 1 < parts.Length) ? parts[i + 1] : null;
Console.WriteLine(name + ": " + value);
}
See fiddle here: https://dotnetfiddle.net/TupvAu
Related Topics
How to "Flatten" or "Index" 3D-Array in 1D Array
Mixing C# & Vb in the Same Project
Observablecollection<> VS. List<>
Built in .Net Algorithm to Round Value to the Nearest 10 Interval
Cell Color Changing in Excel Using C#
How to Set Timer Resolution from C# to 1 Ms
Outofmemoryexception While Populating Memorystream: 256Mb Allocation on 16Gb System
Hashing Passwords with Md5 or Sha-256 C#
Why Do Local Variables Require Initialization, But Fields Do Not
Mocking Iprincipal in ASP.NET Core
Suppressing "Is Never Used" and "Is Never Assigned To" Warnings in C#
When Using Trusted_Connection=True and SQL Server Authentication, Will This Affect Performance
.Net Core 2.2 Can't Be Selected in Visual Studio Build Framework
Convert Array of Bytes to Bitmapimage
The Maximum Number of Characters a Textbox Can Display
How to Deserialize JSON with Duplicate Property Names in the Same Object