How to Handle JSON That Returns Both a String and a String Array

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:

left brace followed by a key string (a name that can't be repeated, in quotes), colon and a value (valid types shown below), followed by an optional comma to add more pairs of string and value at will and finished with a right brace

{ "key": value, "another key": value }

Hint: although javascript accepts single quotes ', JSON only takes double ones ".

[ will start an array:

left bracket followed by value, optional comma to add more value at will and finished with a right bracket

[value, value]

Hint: spaces among elements are always ignored by any JSON parser.

And value is an object, array, string, number, bool or null:

Image showing the 6 types a JSON value can be: string, number, JSON object, Array/list, boolean, and 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



Leave a reply



Submit