Bewahren Sie das Gehäuse auf, wenn Sie Wörterbücher serialisieren

91

Ich habe ein Web-API-Projekt, das wie folgt konfiguriert wird:

config.Formatters.JsonFormatter.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();

Ich möchte jedoch, dass das Gehäuse der Wörterbuchschlüssel unverändert bleibt. Gibt es ein Attribut in Newtonsoft.Jsoneiner Klasse, das angibt, dass das Gehäuse während der Serialisierung unverändert bleiben soll?

public class SomeViewModel
{
    public Dictionary<string, string> Data { get; set; }    
}
zafeiris.m
quelle
1
Haben Sie den Standard-Resolver ausprobiert?
Matthew
1
@ Matthew Nein, habe ich nicht; Können Sie anhand eines Beispiels erklären, wie der Code aussehen würde? Hinweis: Ich möchte weiterhin die Camel-Groß- / Kleinschreibung für alle meine Web-API-Anforderungen. Ich möchte nur eine benutzerdefinierte Serialisierung für eine Klasse (oder möglicherweise für einen beliebigen Wörterbuchschlüssel).
zafeiris.m

Antworten:

132

Es gibt kein Attribut dafür, aber Sie können dies tun, indem Sie den Resolver anpassen.

Ich sehe, dass Sie bereits eine verwenden CamelCasePropertyNamesContractResolver. Wenn Sie daraus eine neue Resolver-Klasse ableiten und die CreateDictionaryContract()Methode überschreiben , können Sie eine Ersatzfunktion bereitstellen DictionaryKeyResolver, die die Schlüsselnamen nicht ändert.

Hier ist der Code, den Sie benötigen würden:

class CamelCaseExceptDictionaryKeysResolver : CamelCasePropertyNamesContractResolver
{
    protected override JsonDictionaryContract CreateDictionaryContract(Type objectType)
    {
        JsonDictionaryContract contract = base.CreateDictionaryContract(objectType);

        contract.DictionaryKeyResolver = propertyName => propertyName;

        return contract;
    }
}

Demo:

class Program
{
    static void Main(string[] args)
    {
        Foo foo = new Foo
        {
            AnIntegerProperty = 42,
            HTMLString = "<html></html>",
            Dictionary = new Dictionary<string, string>
            {
                { "WHIZbang", "1" },
                { "FOO", "2" },
                { "Bar", "3" },
            }
        };

        JsonSerializerSettings settings = new JsonSerializerSettings
        {
            ContractResolver = new CamelCaseExceptDictionaryKeysResolver(),
            Formatting = Formatting.Indented
        };

        string json = JsonConvert.SerializeObject(foo, settings);
        Console.WriteLine(json);
    }
}

class Foo
{
    public int AnIntegerProperty { get; set; }
    public string HTMLString { get; set; }
    public Dictionary<string, string> Dictionary { get; set; }
}

Hier ist die Ausgabe von oben. Beachten Sie, dass alle Namen der Klasseneigenschaften in Kamelform vorliegen, die Wörterbuchschlüssel jedoch ihre ursprüngliche Schreibweise beibehalten haben.

{
  "anIntegerProperty": 42,
  "htmlString": "<html></html>",
  "dictionary": {
    "WHIZbang": "1",
    "FOO": "2",
    "Bar": "3"
  }
}
Brian Rogers
quelle
2
Zu Ihrer Information, PropertyNameResolver ist jetzt veraltet. Es scheint, dass das gut contract.DictionaryKeyResolver = key => key;funktioniert.
John Gietzen
1
Dies ist bei anonymen Typen immer noch SEHR relevant, insbesondere wenn wir für den größten Teil der Struktur eine Kamelhülle wünschen, aber nicht, dass die Schlüssel in Wörterbüchern camelisiert werden.
Chris Schaller
Stimme Chris absolut zu. Ich war gezwungen, in meinem JavaScript die Rahmen zu durchlaufen, nur weil ich nicht verhindern kann, dass Wörterbücher camelCased werden. Es stellt sich heraus, dass eine Codezeile dieses Problem löst (und mein JavaScript viel einfacher macht)!
Stephen Chung
@BrianRogers Funktioniert super! Wissen Sie jedoch, ob ich mit meinem DictionaryKeyResolvernur konditionieren kann, wenn meine Dictionary-Eigenschaft ein benutzerdefiniertes Attribut hat?
Mugen
@Mugen Nicht aus meinem Kopf. Ich würde empfehlen, dies als neue Frage zu stellen. Sie können auf diese Frage zurückgreifen, wenn Sie einen Kontext angeben müssen.
Brian Rogers
65

Json.NET 9.0.1 führte die NamingStrategyKlassenhierarchie ein, um diese Art von Problem zu behandeln. Es extrahiert die Logik für die algorithmische Neuzuordnung von Eigenschaftsnamen aus dem Vertragsauflöser in eine separate, leichtgewichtige Klasse, mit der gesteuert werden kann, ob Wörterbuchschlüssel , explizit angegebene Eigenschaftsnamen und Erweiterungsdatennamen (in 10.0.1 ) neu zugeordnet werden.

Durch Verwenden DefaultContractResolverund Festlegen NamingStrategyeiner Instanz von können CamelCaseNamingStrategySie JSON mit Kamel-ummantelten Eigenschaftsnamen und unveränderten Wörterbuchschlüsseln generieren, indem Sie Folgendes festlegen JsonSerializerSettings.ContractResolver:

var resolver = new DefaultContractResolver
{
    NamingStrategy = new CamelCaseNamingStrategy
    {
        ProcessDictionaryKeys = false,
        OverrideSpecifiedNames = true
    }
};
config.Formatters.JsonFormatter.SerializerSettings.ContractResolver = resolver;

Anmerkungen:

  • Die aktuelle Implementierung von gibt CamelCasePropertyNamesContractResolveraußerdem an, dass .Net-Mitgliedern mit explizit angegebenen Eigenschaftsnamen (z. B. solchen, bei denen JsonPropertyAttribute.PropertyNamesie festgelegt wurden) ihre Namen neu zugeordnet werden sollen:

    public CamelCasePropertyNamesContractResolver()
    {
        NamingStrategy = new CamelCaseNamingStrategy
        {
            ProcessDictionaryKeys = true,
            OverrideSpecifiedNames = true
        };
    }

    Das Obige resolverbewahrt dieses Verhalten. Wenn Sie dies nicht möchten, stellen Sie ein OverrideSpecifiedNames = false.

  • Json.NET verfügt über mehrere integrierte Namensstrategien, darunter:

    1. CamelCaseNamingStrategy. Eine Namensstrategie für Kamelfälle, die die zuvor eingebettete Logik zur Neuzuordnung von Namen enthält CamelCasePropertyNamesContractResolver.
    2. SnakeCaseNamingStrategy. Eine Strategie zur Benennung von Schlangenfällen .
    3. DefaultNamingStrategy. Die Standardbenennungsstrategie. Eigenschaftsnamen und Wörterbuchschlüssel bleiben unverändert.

    Oder Sie können Ihre eigenen erstellen, indem Sie von der abstrakten Basisklasse erben NamingStrategy.

  • Es ist zwar auch möglich, die NamingStrategyeiner Instanz von zu ändern CamelCasePropertyNamesContractResolver, da letztere Vertragsinformationen global für alle Instanzen jedes Typs gemeinsam nutzt CamelCasePropertyNamesContractResolver. Dies kann jedoch zu unerwarteten Nebenwirkungen führen, wenn Ihre Anwendung versucht, mehrere Instanzen von zu verwenden . Es gibt kein solches Problem DefaultContractResolver, daher ist es sicherer, es zu verwenden, wenn eine Anpassung der Gehäuselogik erforderlich ist.

dbc
quelle
Diese Lösung funktioniert nicht für eine Eigenschaft wie public Dictionary<string, Dictionary<string, string>> Values { get; set; }. CamelCase für die inneren Wörterbuchschlüssel wird weiterhin ausgeführt.
Hikalkan
@hikalkan - Obwohl ich Ihr genaues Problem nicht reproduzieren konnte, konnte ich bei Verwendung mehrerer Instanzen von ein Problem finden CamelCasePropertyNamesContractResolver. Grundsätzlich würde NamingStrategyfür die erste die von der zweiten generierten Verträge beeinflusst. Das könnten Sie sehen. Versuchen Sie stattdessen die neue Empfehlung und lassen Sie mich wissen, ob Ihr Problem behoben ist.
dbc
1
Gibt es eine flexible NamingStrategy, so dass es sowohl Kamel- als auch Pascal-Fälle analysieren kann?
Shimmy Weitzhandler
@dbc Was configsoll im ersten Codebeispiel sein?
Ryan Lundy
@ RyanLundy - Ich habe es von der ersten Frage kopiert, die die folgende Codezeile zeigte : config.Formatters.JsonFormatter.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();. Es sieht die MVC 4 Web - API zu sein HttpConfiguration, siehe Wie Satz individuelle JsonSerializerSettings für Json.NET in MVC 4 Web - API? .
dbc
12

Das ist eine sehr schöne Antwort. Aber warum nicht einfach das überschreiben ResolveDictionaryKey?

class CamelCaseExceptDictionaryResolver : CamelCasePropertyNamesContractResolver
    {
        #region Overrides of DefaultContractResolver

        protected override string ResolveDictionaryKey(string dictionaryKey)
        {
            return dictionaryKey;
        }

        #endregion
    }
Dmitriy
quelle
Viel knapp. Danke fürs Teilen.
Abu Abdullah
1

Die ausgewählte Antwort ist perfekt, aber ich denke, wenn ich dies schreibe, muss sich der Vertragsauflöser in so etwas ändern, da DictionaryKeyResolver nicht mehr existiert :)

public class CamelCaseExceptDictionaryKeysResolver : CamelCasePropertyNamesContractResolver
    {
        protected override JsonDictionaryContract CreateDictionaryContract(Type objectType)
        {
            JsonDictionaryContract contract = base.CreateDictionaryContract(objectType);
            contract.PropertyNameResolver = propertyName => propertyName;
            return contract;
        }
    }
Sep.
quelle
4
Eigentlich ist das Gegenteil der Fall. Sie müssen eine alte Version von Json.Net verwenden. DictionaryKeyResolverwurde in Version 7.0.1 hinzugefügt und PropertyNameResolverals veraltet markiert.
Brian Rogers