5

I would like to upset a json array by objects id, for example. let's say I have this json array:

 [{"Id":"1", "a":"1", "b":"2"},
  {"Id":"2", "a":"3", "b":"1"},
  {"Id":"3", "a":"5", "b":"1"}]

And I would like to Upsert it with this array

 [{"Id":"1", "a":"32", "b":"42"},
 {"Id":"2", "a":"3", "b":"1", "c":"23"},
  {"Id":"12", "a":"12", "b":"45"}]

The expected result should be:

[{"Id":"1", "a":"32", "b":"42"},
  {"Id":"2", "a":"3", "b":"1", "c":"23"},
  {"Id":"3", "a":"5", "b":"1"},
  {"Id":"12", "a":"12", "b":"45"}]
marnun
  • 808
  • 8
  • 23

4 Answers4

3

You can simply use Linq on your JArray, as these are IEnumerable<JToken>

var first = JArray.Parse(@"[{'Id':'1', 'a':'1', 'b':'2'},
            {'Id':'2', 'a':'3', 'b':'1'},
            {'Id':'3', 'a':'5', 'b':'1'}]");


var second = JArray.Parse(@"[{'Id':'1', 'a':'32', 'b':'42'},
            {'Id':'2', 'a':'3', 'b':'1', 'c':'23'},
            {'Id':'12', 'a':'12', 'b':'45'}]");

var resultAsEnumerable = first.Concat(second)
                              .GroupBy(t => t["Id"])
                              .Select(g => g.Last());

If you need your result as a JArray, you can easily convert the result to it:

var resultAsJArray = new JArray(resultAsEnumerable.ToArray());
Falanwe
  • 4,636
  • 22
  • 37
2

I think it can be easily done in C#. If you map your entities to something like that:

[DataContract]
public class Entity
{
    [DataMember(Name = "Id")]
    public string Id { get; set; }

    [DataMember(Name = "a")]
    public int? A { get; set; }

    [DataMember(Name = "b")]
    public int? B { get; set; }

    [DataMember(Name = "c")]
    public int? C { get; set; }
}

I think it's not possible to perform desired action using LINQ, but good old foreach will solve your issue.

EDIT: Actually after looking at @vadim-gremyachev answer I think it can be done with LINQ very well:

var l1 = JsonConvert.DeserializeObject<IList<Entity>>(
    @"[{""Id"":""1"", ""a"":""1"", ""b"":""2""}, 
       {""Id"":""2"", ""a"":""3"", ""b"":""1""}, 
       {""Id"":""3"", ""a"":""5"", ""b"":""1""}]");

var l2 = JsonConvert.DeserializeObject<IList<Entity>>(
    @"[{""Id"":""1"", ""a"":""32"", ""b"":""42""},
       {""Id"":""2"", ""a"":""3"", ""b"":""1"", ""c"":""23""},
       {""Id"":""12"", ""a"":""12"", ""b"":""45""}]");

// LINQ
var res = l1.Concat(l2).GroupBy(x => x.Id).Select(x => x.Last()).ToList();

// Foraech
var res2 = new List<Entity>(l1);
foreach (var l2Entity in l2)
{
    var resEntity = res2.FirstOrDefault(x => x.Id == l2Entity.Id);
    if (resEntity == null)
    {
        res2.Add(l2Entity);
    }
    else
    {
        res2[res2.IndexOf(resEntity)] = l2Entity;
    }
}

Then you can just serialize your res list back to JSON and it's done:

var json = JsonConvert.SerializeObject(res);

The resulting JSON will be:

[
    {"Id":"1","a":32,"b":42},
    {"Id":"2","a":3,"b":1,"c":23},
    {"Id":"3","a":5,"b":1},
    {"Id":"12","a":12,"b":45}
]

You can also use l1 and do not create res, it's depending on your case of course. You might also want to order the resulting collection by key after merge is done.

Aleksandr Ivanov
  • 2,778
  • 5
  • 27
  • 35
  • You don't really need an entity to do what's asked, but it could be a good idea nonetheless because I guess there are other manipulations the OP could want to do on the entities, so it's nice to have a strongly typed object. What's really useless though are the `DataContract` and `DataMember` attributes. You can survive with your entities having some lowercase properties. – Falanwe Mar 26 '15 at 11:09
  • In provided JSON there is a strange naming, when `Id` starts with capital letter and other properties not. In such cases `Data` attributes might be a good choice if you want resulting JSON to follow the same rules. – Aleksandr Ivanov Mar 26 '15 at 11:22
0
var x = JArray.Parse(@"[{'Id':'1', 'a':'1', 'b':'2'},
                        {'Id':'2', 'a':'3', 'b':'1'},
                        {'Id':'3', 'a':'5', 'b':'1'}]");


var y = JArray.Parse(@"[{'Id':'1', 'a':'32', 'b':'42'},
                        {'Id':'2', 'a':'3', 'b':'1', 'c':'23'},
                        {'Id':'12', 'a':'12', 'b':'45'}]");

//1. Union arrays skipping items that already exist
x.Merge(y, new JsonMergeSettings
        {
            MergeArrayHandling = MergeArrayHandling.Union,
        });

//2. Get distinct items by key (Id) 
var result = x.GroupBy(i => i["Id"]).Select(g => g.Last()).ToList();
Vadim Gremyachev
  • 57,952
  • 20
  • 129
  • 193
0

I tried a few of these answers and they only partly worked for me, ordering the result json is left for a exercise for the reader, you probably would want the input json to not have ID as string for this to work properly. Tested using .net core 3.1.

var input1 = JsonConvert.DeserializeObject<JArray>(@"[  {'Id':'1', 'a':'1', 'b':'2'},
                                                        { 'Id':'2', 'a':'3', 'b':'1'},
                                                        { 'Id':'3', 'a':'5', 'b':'1'}]");

var input2 = JsonConvert.DeserializeObject<JArray>(@" [  {'Id':'1', 'a':'32', 'b':'42'},
                                                         {'Id':'2', 'a':'3', 'b':'1', 'c':'23'},
                                                         {'Id':'12', 'a':'12', 'b':'45'}]");

//you may want this the other way depending on what your trying to do
//input1.Merge(input2);
input2.Merge(input1);

var res = new List<dynamic>();

foreach (var x in input2.GroupBy(x => x["Id"]).ToList())
{
      var newItem = new ExpandoObject();
      foreach (var y in x)
      {
            foreach (JProperty z in y)
            {
                 newItem.TryAdd(z.Name, z.Value);
            }
       }
       res.Add(newItem);

 }

 Console.WriteLine(JsonConvert.SerializeObject(res));
 Console.ReadLine();

Output:

[{
        "Id": "1",
        "a": "32",
        "b": "42"
    }, {
        "Id": "2",
        "a": "3",
        "b": "1",
        "c": "23"
    }, {
        "Id": "12",
        "a": "12",
        "b": "45"
    }, {
        "Id": "3",
        "a": "5",
        "b": "1"
    }
]