简体   繁体   中英

How to serialize an object by Linq Expression

I want to serialize an object by linq expression. Let's pretend, i've got the following classes:

public class User
{
    public string Username { get; set; }

    public string Password { get; set; }

    public IList<Usergroup> Usergroups { get; set; }
}

public class Usergroup
{
    public string Name { get; set; }

    public List<User> Users { get; set; }

    public List<AccessRight> AccessRights { get; set; }

    public Screen Screen { get; set; }
}

public class AccessRight
{
    public int AccessLevel { get; set; }
}

public class Screen
{
    public string Name { get; set; }
}

Now i have an object User and want to serialize it with its Usergroups, and for all Usergroup its AccessRights and Screen, but without the List of other Users.

Is there a way (or usable Git/NuGet project) to do this with linq expression. Like:

var string = Serialize<User>(user, u => u.Usergroups, u => u.Usergroups.Select(ug => ug.AccessRights), ...)

When i searched for json.net and Expression, i found only solutions how to serialize an expression itself.

I want to use the solution to initialize and unproxy NHibernate entities.

Thanks in advance

Did you tried Json.NET NuGet?

Lets say that I have the following data:

var user = new User()
{
   Username = "User 1",
   Password = "***",
   Usergroups = new List<Usergroup>()
   {
      new Usergroup()
      {
        Name = "Administrator",
        Screen = new Screen() { Name = "Users" },
        AccessRights = new List<AccessRight>()
        {
            new AccessRight() { AccessLevel = 9999 }
        },
        Users = new List<User>()
        {
            new User()
            {
                Password = "@#$%",
                Usergroups = new List<Usergroup>(),
                Username = "User 2"
            }
        }
    }
   }
};

//Here you serialize your object.
var json = JsonConvert.SerializeObject(user);

For the property Users in the object Usergroup I put the attribute: [JsonIgnore] .

public class Usergroup
{
    public string Name { get; set; }

    [JsonIgnore]
    public List<User> Users { get; set; }

    public List<AccessRight> AccessRights { get; set; }

    public Screen Screen { get; set; }
}

Expected result

{
  "Username": "User 1",
  "Password": "***",
  "Usergroups": [{
     "Name": "Administrator",
     "AccessRights": [
        {
          "AccessLevel": 9999
        }
      ],
     "Screen": {
       "Name": "Users"
      }
     }]
 }

The easiest way here is to use anonymous types, when serialising we only need to define a structure that is similar to the expected type, it doesn't have to be an exact clone to support deserialization.
We can easily omit properties in a constructed anonymous type and then serialize that.

I say that this method is 'easy' because it doesn't involve modifications to the model definition or declarations of DTOs. This is a non-invasive solution that can be applied to all models in serialization scenarios where you do not need or want the full object graph to be serialized.

Lets take nhibernate out of the picture for now, if you have an instance of your User class that you want to serialize you can simply use this.

var simpleCereal = Newtonsoft.Json.JsonConvert.SerializeObject(new { user.Username, user.Password, UserGroups = user.Usergroups.Select(ug => new { ug.AccessRights, ug.Name, ug.Screen }).ToList() });

Even though we used anonymous types, the serialization result will still deserialize back into a valid User object if you specify to ignore the missing members in the JsonSerializerSettings
new JsonSerializerSettings { MissingMemberHandling = MissingMemberHandling.Ignore });

We can achieve the syntax that you have suggested through a simple helper method that accepts a lambda:

/// <summary>
/// Serialize an object through a lambda expression that defines the expected output structure
/// </summary>
/// <typeparam name="T">Type of the object to serialize</typeparam>
/// <param name="target">The target object to serialize</param>
/// <param name="expr">The lambda expression that defines the final structure of the serialized object</param>
/// <returns>Serialized lambda representation of the target object</returns>
public static string Serialize<T>(T target, System.Linq.Expressions.Expression<Func<T, object>> expr)
{
    var truncatedObject = expr.Compile().Invoke(target);
    return Newtonsoft.Json.JsonConvert.SerializeObject(truncatedObject);
}

Your syntax is now supported:

string lambdaCereal = Serialize(user, u => new { u.Username, u.Password, UserGroups = u.Usergroups.Select(ug => new { ug.AccessRights, ug.Name, ug.Screen }).ToList() });

The following is the code that I used to test this, OPs class definitions have been omitted, you can find them in the question.

static void Main(string[] args)
{
    User user = new User
    {
        Username = "Test User",
        Password = "Password",
        Usergroups = new List<Usergroup>
        {
            new Usergroup
            {
                Name = "Group12", AccessRights = new List<AccessRight>
                {
                    new AccessRight { AccessLevel = 1 },
                    new AccessRight { AccessLevel = 2 }
                },
                Screen = new Screen { Name = "Home" },
                Users = new List<User>
                {
                    new User { Username = "Other1" },
                    new User { Username = "Other2" }
                }
            },
            new Usergroup
            {
                Name = "Group3Only", AccessRights = new List<AccessRight>
                {
                    new AccessRight { AccessLevel = 3 },
                },
                Screen = new Screen { Name = "Maintenance" },
                Users = new List<User>
                {
                    new User { Username = "Other1" },
                    new User { Username = "Other2" }
                }
            }
        }
    };
    // Standard deep serialization, will include the deep User objects within the user groups.
    var standardCereal = Newtonsoft.Json.JsonConvert.SerializeObject(user);
    // Simple anonymous type serialize, exclude Users from within UserGroups objects
    var simpleCereal = Newtonsoft.Json.JsonConvert.SerializeObject(new { user.Username, user.Password, UserGroups = user.Usergroups.Select(ug => new { ug.AccessRights, ug.Name, ug.Screen }).ToList() });
    // Same as above but uses a helper method that accepts a lambda expression
    var lambdaCereal = Serialize(user, u => new { u.Username, u.Password, UserGroups = u.Usergroups.Select(ug => new { ug.AccessRights, ug.Name, ug.Screen }).ToList() });
    // NOTE: simple and lambda serialization results will be identical. 
    // deserialise back into a User
    var userObj = Newtonsoft.Json.JsonConvert.DeserializeObject<User>(
        lambdaCereal, 
        new Newtonsoft.Json.JsonSerializerSettings
        {
            MissingMemberHandling = Newtonsoft.Json.MissingMemberHandling.Ignore
        });
}

/// <summary>
/// Serialize an object through a lambda expression that defines the expected output structure
/// </summary>
/// <typeparam name="T">Type of the object to serialize</typeparam>
/// <param name="target">The target object to serialize</param>
/// <param name="expr">The lambda expression that defines the final structure of the serialized object</param>
/// <returns>Serialized lambda representation of the target object</returns>
public static string Serialize<T>(T target, System.Linq.Expressions.Expression<Func<T, object>> expr)
{
    var truncatedObject = expr.Compile().Invoke(target);
    return Newtonsoft.Json.JsonConvert.SerializeObject(truncatedObject);
}

In the above code simpleCereal == lambdaCereal => true

lambdaCereal results in this:

{
  "Username": "Test User",
  "Password": "Password",
  "UserGroups": [
    {
      "AccessRights": [
        {
          "AccessLevel": 1
        },
        {
          "AccessLevel": 2
        }
      ],
      "Name": "Group12",
      "Screen": {
        "Name": "Home"
      }
    },
    {
      "AccessRights": [
        {
          "AccessLevel": 3
        }
      ],
      "Name": "Group3Only",
      "Screen": {
        "Name": "Maintenance"
      }
    }
  ]
}

You mentioned that you are using nhibernate, if the object is not yet materialised in memory, then if you do not expand the Users property on the UserGroups expansion (and therefor not eagerly load the Users property), then it will not be included in the serialization output.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM