Référence CrystalJson
Les tables de référence du travail quotidien : le setup du générateur de source, les attributs que vous posez sur un type, les settings que vous passez à un appel, et les diagnostics de build que vous pouvez rencontrer. Pour les guides pratiques, voir Travailler avec CrystalJson ; pour la conception, voir Ce que c'est, et pourquoi. Quand un comportement a changé entre deux versions, le guide de migration 7.4.2 vers 7.4.3 porte l'histoire complète, et cette page y renvoie plutôt que de la répéter.
Tous les exemples utilisent using SnowBank.Data.Json;.
Setup
Les types d'exécution de CrystalJson (JsonValue, CrystalJson, CrystalJsonSettings) vivent dans
SnowBank.Core. Un projet qui référence SnowBank.Core sérialise, parse et utilise le DOM via le
chemin par réflexion sans autre setup.
Les convertisseurs générés et les proxies typés demandent en plus le générateur de source. Il est
distribué comme un package séparé que le compilateur exécute comme un analyzer Roslyn. C'est un outil
de build, pas une partie de votre application livrée ; SnowBank.Core est la seule dépendance
d'exécution :
<!-- exécution : le DOM JsonValue et l'API CrystalJson -->
<PackageReference Include="SnowBank.Core" Version="7.4.6" />
<!-- build seulement : le générateur de source, un analyzer Roslyn, non redistribué avec votre app -->
<PackageReference Include="SnowBank.Serialization.Json.CodeGen" Version="7.4.6" />
Donnez aux deux la même version que vos autres packages SnowBank, ou omettez la version en gestion
centralisée des packages. Le générateur demande C# 9 ou plus : en dessous, il rapporte SYSLIB1221
et n'émet rien, JSON compris, donc mettez <LangVersion> à 9 ou plus (le déclencheur classique est un
projet porté qui pinne encore un <LangVersion>7.3</LangVersion> d'époque .NET Framework).
Un container est une classe partial qui déclare les types qu'elle sérialise. Les membres générés
atterrissent dedans :
[CrystalJsonConverter]
[CrystalSerializable(typeof(Book))]
public static partial class AcmeSerializers { }
string json = AcmeSerializers.Book.ToJsonText(book);
Book back = AcmeSerializers.Book.Deserialize(json);
Attributs de container
Le container déclare trois choses indépendantes : quelle classe héberge le code, quels formats elle produit, et quels types elle enregistre.
| Attribut | Namespace | Rôle |
|---|---|---|
[CrystalConverter] |
SnowBank.Data |
le marqueur de container ; ne nomme aucun format à lui seul |
[CrystalSerializable(typeof(T))] |
SnowBank.Data |
enregistre un type ; répétable ; alimente chaque format que le container produit |
[CrystalJsonOutput(...)] |
SnowBank.Data.Json |
demande le format JSON et porte ses paramètres (profil, politique de nommage) |
[CrystalJsonConverter(...)] |
SnowBank.Data.Json |
alias : [CrystalConverter] + [CrystalJsonOutput] avec les mêmes paramètres, pour un container JSON seul |
[CrystalJsonSelfSerializable] |
SnowBank.Data.Json |
méta-attribut pour les types auto-sérialisables (un type sert de son propre container) ; voir le guide de migration |
Un profil passé à [CrystalJsonOutput(...)] ou [CrystalJsonConverter(...)] fixe la forme de sortie
par défaut du container, CrystalJsonSerializerDefaults.Web pour le camelCase,
.DataContractCompat pour le format historique de DataContractJsonSerializer. Des settings passés
au site d'appel remplacent le profil pour cet appel.
[CrystalSerializable] remplace l'ancien [CrystalJsonSerializable] ; l'enregistrement est désormais
neutre vis-à-vis du format. La sortie XML a ses propres attributs et sa propre page,
CrystalXml.
Attributs de membre
Posez-les sur une propriété ou un champ pour changer la sérialisation de ce seul membre. Tous sont honorés sur le chemin par réflexion comme sur le chemin généré.
| Attribut | Effet |
|---|---|
[JsonProperty("name")] |
renomme le membre en sortie |
[JsonProperty(DefaultValue = ...)] |
déclare le défaut du membre, utilisé par la condition d'ignore WhenWritingDefault |
[JsonProperty(EnumFormat = JsonEnumFormat.Number)] |
écrit cet enum comme son nombre plutôt que son nom |
[JsonProperty(NumberFormat = JsonNumberFormat.String)] |
écrit ce nombre comme une string ("12345678901234567"), ce qui protège les valeurs 64 bits de la perte de précision JavaScript |
[JsonBooleanLiterals(whenFalse, whenTrue)] |
littéraux sur mesure pour un booléen ; un littéral false null omet le membre quand il est false. Les arguments sont une string, un bool, ou un nombre |
[JsonIgnore] |
exclut le membre (inconditionnel) |
[JsonIgnore(Condition = ...)] |
exclusion conditionnelle ; voir la table ci-dessous |
[JsonConvertWith(typeof(X))] |
sérialise le membre via le convertisseur X (implémente IJsonPacker<T> et/ou IJsonDeserializer<T>) |
[JsonInclude] |
inclut un membre non-public sur un type sans [DataContract] |
[IgnoreDataMember] |
exclut le membre sur un type sans [DataContract] |
[JsonIgnore(Condition = ...)] lit JsonIgnoreCondition, en suivant le sens de System.Text.Json.
Attention au piège de nommage : Never veut dire « ne jamais ignorer ».
| Condition | Effet |
|---|---|
Always (le défaut) |
membre exclu |
Never |
membre toujours émis, passant outre les suppressions de null et de défaut au niveau des settings |
WhenWritingNull |
omis seulement quand la valeur est null |
WhenWritingDefault |
omis seulement quand la valeur égale le défaut du membre |
Pour les types [DataContract], [DataMember(Name = ...)] renomme et
[DataMember(IsRequired = true)] fait throw à la lecture quand le membre est absent. Les containers
générés appliquent le modèle d'appartenance DataContract depuis la 7.4.3 ; le
guide de migration en donne le détail.
Attributs d'autres sérialiseurs
CrystalJson lit les attributs qu'un DTO existant porte déjà de System.Text.Json et de Newtonsoft.Json (JSON.NET), donc un type porté se sérialise sans réannotation, à l'identique sur les deux chemins :
| Attribut étranger | Interprété par CrystalJson comme |
|---|---|
System.Text.Json [JsonPropertyName("x")] |
un renommage, comme [JsonProperty("x")] |
Newtonsoft [JsonProperty("x")] |
un renommage |
[JsonIgnore], l'une ou l'autre orthographe |
exclut le membre |
System.Text.Json [JsonIgnore(Condition = ...)] |
exclusion conditionnelle, les conditions ci-dessus |
System.Text.Json [JsonInclude] |
inclut un membre non-public |
[JsonConverter(typeof(X))], l'une ou l'autre orthographe |
exécute X, quand il implémente IJsonPacker<T> et/ou IJsonDeserializer<T> |
[DataContract] / [DataMember] |
le modèle d'appartenance DataContract |
Quand plusieurs attributs de nommage s'accordent, le nom effectif vient du plus prioritaire :
CrystalJson [JsonProperty], puis [JsonPropertyName], puis Newtonsoft [JsonProperty]. Deux
attributs de nommage qui divergent sont une erreur de build (CJSON0011) : un type ne peut pas servir
deux contrats de sortie. Un [DataMember] sur un type [DataContract] compte comme l'un d'eux même
nu, parce qu'il nomme le membre d'après lui-même : un [JsonProperty] qui renomme ce même membre est
le même conflit. Un [JsonConverter] étranger qui nomme un type n'implémentant pas le contrat
de convertisseur CrystalJson est ignoré, pas une erreur, donc un DTO à moitié porté reste
sérialisable. Le guide de migration donne les règles d'interop
complètes.
Settings
Passez un CrystalJsonSettings à un appel Serialize, Parse, ou Deserialize. Partez d'un preset
et ajoutez des modificateurs fluents ; chaque modificateur rend une nouvelle instance en cache.
Presets :
| Preset | Sortie |
|---|---|
CrystalJsonSettings.Json |
le défaut : du JSON lisible |
CrystalJsonSettings.JsonCompact |
sans espaces |
CrystalJsonSettings.JsonIndented |
multi-lignes, indenté |
CrystalJsonSettings.JsonStrict |
rejette les commentaires et les virgules finales à la lecture |
CrystalJsonSettings.JsonReadOnly |
parse vers des valeurs figées |
CrystalJsonSettings.DataContractCompat |
reproduit la sortie de DataContractJsonSerializer |
Modificateurs courants :
| Modificateur | Effet |
|---|---|
.ThrowOnDuplicateFields() |
une clé répétée est une erreur à la lecture, pas un last-wins |
.WithoutComments() |
rejette les commentaires JavaScript à la lecture |
.WithoutTrailingCommas() |
rejette une virgule finale à la lecture |
.WithEnumAsNumbers() / .WithEnumAsStrings() |
écrit les enums comme leur nombre, ou leur nom (le défaut) |
.WithNullMembers() / .WithoutNullMembers() |
émet ou omet les membres dont la valeur est null |
.WithoutDefaultValues() |
omet les membres qui égalent leur défaut |
.WithMicrosoftDates() / .WithIso8601Dates() |
format des dates en sortie |
.WithIso8601Durations() / .WithNumericDurations() |
TimeSpan en "P1DT2H3M4S", ou en nombre de secondes (le défaut) |
.WithDictionariesAsPairArrays() / .WithDictionariesAsMaps() |
un dictionnaire comme un tableau de {"Key":..,"Value":..}, ou comme un objet JSON (le défaut) |
JsonStrict ne couvre pas les clés dupliquées ; ajoutez .ThrowOnDuplicateFields() quand une clé
répétée doit échouer. Pour durcir le parser face à un input non fiable, voir
Durcir le parsing d'un input non fiable.
Pour lire plusieurs documents consécutifs dans un même buffer, utilisez CrystalJson.ParseFragment,
pas WithTrailingData() (qui parse la première valeur et jette le reste).
Défauts
- Les enums se sérialisent par leur nom, pas leur nombre. La lecture accepte les noms (insensible à la casse), les nombres et les nombres en string, quels que soient les settings.
- Le parser est permissif par défaut : les commentaires JavaScript et les virgules finales sont
acceptés. C'est faux pour un input que vous ne contrôlez pas ; resserrez-le avec
JsonStrict. - Les nombres gardent leur littéral d'origine sur la route DOM tant que vous ne les lisez pas comme une valeur typée.
Diagnostics
Les codes CJSON#### ci-dessous sont ceux qu'un auteur normal rencontre en écrivant des DTO. Chacun
est rapporté au même endroit par les deux chemins : le générateur émet le diagnostic, et le chemin
par réflexion throw le même message quand il construit le contrat du type. Le
guide de migration donne le traitement complet de chacun.
| Id | Sévérité | Rejette | Remède |
|---|---|---|---|
CJSON0008 |
Error | un [JsonIgnore] inconditionnel à côté d'un signal d'inclusion ([DataMember], [JsonInclude], un attribut de nommage) |
scinder en un DTO par format, ou retirer un des deux attributs |
CJSON0010 |
Error | [JsonConvertWith] nomme un type qui n'implémente ni IJsonPacker<T> ni IJsonDeserializer<T> |
implémenter une facette de convertisseur, ou corriger le type nommé |
CJSON0011 |
Error | un membre déclare deux noms différents pour deux sérialiseurs | un DTO par format, chacun avec un seul jeu cohérent d'attributs |
CJSON0012 |
Warning | un membre internal sans signal d'inclusion ni d'exclusion, sérialisé par le générateur mais invisible au chemin par réflexion |
ajouter [JsonInclude] ou [JsonIgnore] pour fixer l'intention |
CJSON0013 |
Error | le profil DataContractCompat combiné à une politique de nommage |
retirer la politique de nommage ; le profil fixe les noms |
CJSON0015 |
Error | un callback de sérialisation qui prend un StreamingContext |
retirer le paramètre, ou le remplacer par JsonValue, JsonObject, ou JsonArray |
CJSON0016 |
Error | [OnDeserializing] sur un type avec un membre required ou init-only |
retirer [OnDeserializing], ou rendre le membre assignable |
CJSON0017 |
Error | un argument de [JsonBooleanLiterals] qui n'est ni string, ni bool, ni nombre |
utiliser un littéral valide |
CJSON0018 |
Warning | StrictLiterals = true avec un littéral false null (rien à imposer du côté false) |
retirer StrictLiterals, ou donner au membre un vrai littéral false |
CJSON0019 |
Warning | une inscription [CrystalSerializable] d'un type que CrystalJson sérialise déjà nativement |
retirer l'inscription |
CJSON0022 |
Error | [DataMember] sur une implémentation explicite d'interface : le membre appartient au contrat, et le code généré ne peut pas déclarer d'accesseur pour un nom de membre qualifié |
le promouvoir en membre normal, ou déplacer le contrat sur un DTO dédié |
CJSON0027 |
Error | un type qui n'a pas de constructeur sans paramètre et dont aucun constructeur ne peut être choisi : deux constructeurs publics correspondent aux membres et aucun ne porte [JsonConstructor], [JsonConstructor] est sur deux d'entre eux, ou un paramètre de constructeur ne correspond à aucun membre sérialisé |
marquez un constructeur avec [JsonConstructor], ou alignez le nom du paramètre sur un membre |
Les diagnostics des types auto-sérialisables (CJSON0004 à CJSON0007, CJSON0020, CJSON0021) et
les codes du générateur XML (CRYS####, CXML####) sont couverts dans le
guide de migration et CrystalXml.