JSON et CSharp

De Banane Atomic
Aller à la navigationAller à la recherche

Liens

  • json2csharp, générer des classes C# à partir de JSON

Contexte

Csharp.svg
class MaClasse
{
    public string Property1 { get; set; }
    public string Property2 { get; set; }
}
Javascript.svg
{
    "Property1": "Value1",
    "Property2": "Value2"
}

JsonSerializer (System.Text.Json)

Replacement of Newtonsoft.Json
The library is built-in as part of the .NET Core 3.0 shared framework.
Doesn't serialize Dictionary is key is not a string.
Cs.svg
using System.Text.Json;

var jsonSerializerOptions = new JsonSerializerOptions
{
    PropertyNamingPolicy = JsonNamingPolicy.CamelCase,            // write property names in camelCase
    DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, // don't write properties with null values
    Converters = { new JsonStringEnumConverter() }
};

var json = JsonSerializer.Serialize(myObject, jsonSerializerOptions);
var myObject = JsonSerializer.Deserialize<MyClass>(json, jsonSerializerOptions);

Custom converter

Cs.svg
public class DateTimeJsonConverter : JsonConverter<DateTime>
{
    private const string dateTimeFormat = "dd_MM_yyyy";

    public override DateTime Read(
        ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
        => DateTime.Parse(reader.GetString()!, dateTimeFormat, CultureInfo.InvariantCulture);

    public override void Write(
        Utf8JsonWriter writer, DateTime dateTimeValue, JsonSerializerOptions options)
        => writer.WriteStringValue(dateTimeValue.ToString(dateTimeFormat, CultureInfo.InvariantCulture));
}

Attributes

Cs.svg
class MyClass
{
    [JsonPropertyName("PropertyX")]
    public string Property1 { get; set; }

    [JsonIgnore]
    public string Property2 { get; set; }
}

JavaScriptSerializer

Pas de pretty print
Csharp.svg
using System.Web.Script.Serialization; // System.Web.Extensions.dll

var mc = new MaClasse() { Property1 = "Value1", Property2 = "Value2" };

// sérialisation
string json = new JavaScriptSerializer().Serialize(mc);

// désérialisation
MaClasse mc2 = new JavaScriptSerializer().Deserialize<MaClasse>(json);

Newtonsoft.Json

Now use JsonSerializer
Csharp.svg
using Newtonsoft.Json;

// lecture du fichier
var jsonContent = File.ReadAllText("/chemin/vers/le/fichier.json");

// Désérialiser
MaClasse monObjet = JsonConvert.DeserializeObject<MaClasse>(text);

// sérialiser avec pretty print
var json = JsonConvert.SerializeObject(monObjet, Formatting.Indented);


class MaClasse
{
    // Forcer cette propriété en deuxième position
    [JsonProperty(Order = 2)]
    public string P1 { get; set; }

    // Forcer cette propriété en première position
    [JsonProperty(Order = 1)]
    public string P2 { get; set; }

    // Ne pas sérialiser cette propriété
    [JsonIgnore]
    public string P3 { get; set; }

    // Ne pas sérialiser cette propriété si sa valeur est nulle
    [JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
    public string P31 { get; set; }

    public IList<string> P32 { get; private set; } = new List<string>();
    // Ne pas sérialiser cette propriété si la liste est vide
    public bool ShouldSerializeP32()
    {
        return P32.Count > 0;
    }

    // mapping de la propriété avec odata.p4 en sérialisation et désérialisation
    [JsonProperty(PropertyName = "odata.p4")]
    public string P4 { get; set; }
L'ordre par défaut est de -1

JsonSerializerSettings

Csharp.svg
// Affiche les types .NET dans le json généré
var jsonSerializerSettings = new JsonSerializerSettings() { TypeNameHandling = TypeNameHandling.All };
JsonConvert.SerializeObject(data, Formatting.Indented, jsonSerializerSettings);
Javascript.svg
{
  "$type": "System.Collections.Generic.List`1[[MonNamespace.MaClasse, MonAssembly]], mscorlib",
  "$values": [
    {
      "$type": "MonNamespace.MaClasse, MonAssembly",
      "MaPropriété": "valeur"
    }
  ]
}

JObject

JObject collection de JProperties
JProperties contient un Name string et une Value JToken
JToken valeur JSON générique: string, JArray, JObject, JProperty
Js.svg
{
  "prop1": "value1",
  "prop2": "2.34",
  "prop3": [
    {
      "sousprop1": "value11",
      "sousprop2": "11.34"
    },
    {
      "sousprop1": "value12",
      "sousprop2": "12.34"
    }
  ]
}
Csharp.svg
JObject jo = JObject.Parse(json);
string jsonText = jo.ToString();

// dynamic
dynamic d = JObject.Parse(json);
d.prop2;  // 2.34

// Parse et ToObject
JArray jArray = JArray.Parse(json);
List<MyClass> myObjects = jArray.ToObject<List<MyClass>>();

// path
string value11 = jo.SelectToken("prop3[0].sousprop1").Value<string>();  // value11
string value11 = (string)jo.SelectToken("prop3[0].sousprop1");  // cast au lieu du Value<T>()

// élément du tableau prop3 correspondant au filtre (JObject)
JToken token = jo.SelectToken("$.prop3[?(@.sousprop1 == 'value12')]");

// élément du tableau prop3 correspondant au filtre (JObject)
JToken token = jo.SelectToken("$.prop3[?(@.sousprop2 > 12)]");
// JSONException si plusieurs éléments correspondent au filtre

// éléments du tableau prop3 correspondant au filtre (JObject)
IEnumerable<JToken> tokens = jo.SelectTokens("$.prop3[?(@.sousprop2 > 11)]");
// toutes les sousprop2 filles de prop3
IEnumerable<JToken> tokens = jo.SelectTokens("$.prop3[?(@.sousprop2)].sousprop2");

JToken token = jo.SelectToken("$.prop3[?(@.sousprop1 == 'value12')].sousprop2");  // 12.34
token.Replace("13");

foreach (JProperty property in jo.Properties())
{
    string name = property.Name;  // prop1
    JToken valueToken = property.Value;
    if (valueToken.Type == JTokenType.String)
    {
        string valueString = valueToken.Value<string>();  // value1
    }
    else if (valueToken.Type == JTokenType.Array)
    {
        JArray valueArray = valueToken.Value<JArray>();
        foreach (JObject sousJo in valueArray.Children<JObject>())
        {
            foreach (JProperty sousProperty in sousJo.Properties())
            {
                string sousName = sousProperty.Name;  // sousprop1
                JToken sousValueToken = sousProperty.Value;
                if (sousValueToken.Type == JTokenType.String)
                {
                    string sousValueString = sousValueToken.Value<string>();  // value11
                }
            }
        }
    }
}

JsonConverter

Permet de modifier le comportement de sérialisation.

Cs.svg
[JsonConverter(typeof(MyListJsonConverter))]
public class MyList
{
    public int Prop1 { get; set; }
    public int Prop2 { get; set; }
}

public class MyListJsonConverter : JsonConverter
{
    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        var myList = (MyList) value;
        serializer.Serialize(writer, new int[] { myList.Prop1, myList.Prop2 });
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        var ml = new MyList();

        if (reader.TokenType != JsonToken.Null)
        {
            if (reader.TokenType == JsonToken.StartArray)
            {
                JToken token = JToken.Load(reader);
                var values = token.ToObject<List<int>>();
                ml.Prop1 = values[0];
                ml.Prop2 = values[1];
            }
        }

        return ml;
    }

    public override bool CanConvert(Type objectType)
    {
        return typeof(MyList).IsAssignableFrom(objectType);
    }
}
Json.svg
// sérialisation custom
[
  [ 1, 11 ],
  [ 2, 22 ]
]

// sérialisation par défaut
[
  { "Prop1": 1, "Prop2": 11 },
  { "Prop1": 2, "Prop2": 22 },
]

Désérialiser dans une classe abstraite

On souhaite désérialiser dans une classe abstraite, mais le mécanisme de désérialisation ne peut créer cette classe abstraite.
Il faut donc surcharger la méthode ReadJson du JsonConverter pour lui permettre de choisir quelle classe concrète doit être utilisée.

Csharp.svg
public abstract class A
{
    public abstract string P1 { get; set; }
}

public class B : A
{
    public override string P1 { ... }
}

public class C : A
{
    public override string P1 { ... }
}

public class AJsonConverter : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return typeof(A).IsAssignableFrom(objectType);
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        JObject item = JObject.Load(reader);
        if (item["P1"].Value<string>() == "...")
        {
            return item.ToObject<B>();
        }
        else
        {
            return item.ToObject<C>();
        }
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }
}

var monObjet = JsonConvert.DeserializeObject<A>(jsonText, new AJsonConverter());

Fichier JSON

Csharp.svg
// écriture des items dans jsonFilePath
File.WriteAllText(jsonFilePath, JsonConvert.SerializeObject(items, Formatting.Indented));

// lecture de jsonFilePath dans items
using (StreamReader file = File.OpenText(jsonFilePath))
{
    var serializer = new JsonSerializer();
    items = (List<Item>)serializer.Deserialize(file, typeof(List<Item>));
}