Code Monkey home page Code Monkey logo

elixir-map_diff's Introduction

MapDiff

hex.pm version Build Status Inline docs

Calculates the difference between two (nested) maps.

The idea is very simple: One of four things can happen to each key in a map:

  • It remains the same: :equal
  • It was not in the original map, but it is in the new one: :added
  • It was in the original map, but is no longer in the new one: :removed
  • It is in both maps, but its value changed.

For the fourth variant, MapDiff.diff/2 returns :primitive_change if the value under the key was 'simply changed', and :map_change if in both arguments this value itself is a map, which means that MapDiff.diff/2 was called on it recursively.

In the case of a :map_change, the resulting map also contains two extra keys: :added and :removed, which are maps containing all key-value pairs that were added, removed or changed (in this case, they are listed in both maps) at this level. Keys whose values remained equal are thus not listed in these maps.

In the case of a :primitive_change, the resulting map also contains :added and :removed, but they simply contain the before and after versions of the value stored.

MapDiff.diff/2 is the single function that MapDiff currently exports.

It returns a 'patch', which is a map describing the changes between map_a and map_b. This patch always has the key :changed, the key :value and if :changed is :map_change, the keys :added and :removed.

Examples

If the (nested) map is still the same, it is considered :equal:

iex> MapDiff.diff(%{my: 1}, %{my: 1})
%{changed: :equal, value: %{my: 1}}

When a key disappears, it is considered :removed:

iex> MapDiff.diff(%{a: 1}, %{})
%{changed: :map_change,
value: %{a: %{changed: :removed, value: 1}}, added: %{}, removed: %{a: 1}}

When a key appears, it is considered :added:

iex> MapDiff.diff(%{}, %{b: 2})
%{changed: :map_change, value: %{b: %{changed: :added, value: 2}}, added: %{b: 2}, removed: %{}}

When the value of a key changes (and one or both of the old or new values is not a map), then this is considered a :primitive_change.

iex> MapDiff.diff(%{b: 3}, %{b: 2})
%{changed: :map_change, value: %{b: %{added: 2, changed: :primitive_change, removed: 3}}, added: %{b: 2}, removed: %{b: 3}}
iex> MapDiff.diff(%{val: 3}, %{val: %{}})
%{changed: :map_change, value: %{val: %{added: %{}, changed: :primitive_change, removed: 3}}, added: %{val: %{}}, removed: %{val: 3}}

When the value of a key changes, and the old and new values are both maps, then this is considered a :map_change that can be parsed recursively.

iex> MapDiff.diff(%{a: %{}}, %{a: %{b: 1}})
%{changed: :map_change, 
    value: %{a: %{
      added: %{b: 1}, changed: :map_change, removed: %{},
      value: %{b: %{changed: :added, value: 1}}}
    }, 
    added: %{a: %{b: 1}}, 
    removed: %{a: %{}}}

A more complex example, to see what happens with nested maps:

iex> foo = %{a: 1, b: 2, c: %{d: 3, e: 4, f: 5}}
iex> bar = %{a: 1, b: 42, c: %{d: %{something_else: "entirely"}, f: 10}}
iex> MapDiff.diff(foo, bar)
%{added: %{b: 42, c: %{d: %{something_else: "entirely"}, f: 10}},
  changed: :map_change, removed: %{b: 2, c: %{d: 3, e: 4, f: 5}},
  value: %{a: %{changed: :equal, value: 1},
    b: %{added: 42, changed: :primitive_change, removed: 2},
    c: %{added: %{d: %{something_else: "entirely"}, f: 10},
      changed: :map_change, removed: %{d: 3, e: 4, f: 5},
      value: %{d: %{added: %{something_else: "entirely"},
          changed: :primitive_change, removed: 3},
        e: %{changed: :removed, value: 4},
        f: %{added: 10, changed: :primitive_change, removed: 5}}}}}

It is also possible to compare two structs of the same kind. MapDiff.diff/2 will add a struct_name field to the output, so you are reminded of the kind of struct whose fields were changed.

For example, suppose you define the following structs:

defmodule Foo do
  defstruct a: 1, b: 2, c: 3
end

defmodule Bar do
  defstruct a: "foo", b: "bar", z: "baz"
end

Then the fields of one Foo struct can be compared to another:

iex> MapDiff.diff(%Foo{}, %Foo{a: 3})
%{added: %Foo{a: 3, b: 2, c: 3}, changed: :map_change,
removed: %Foo{a: 1, b: 2, c: 3}, struct_name: Foo,
value: %{a: %{added: 3, changed: :primitive_change, removed: 1},
  b: %{changed: :equal, value: 2}, c: %{changed: :equal, value: 3}}}

When comparing two different kinds of structs, this of course results in a :primitive_change, as they are simply considered primitive data types.

iex> MapDiff.diff(%Foo{}, %Bar{})
%{added: %Bar{a: "foo", b: "bar", z: "baz"}, changed: :primitive_change,
  removed: %Foo{a: 1, b: 2, c: 3}}

Installation

The package can be installed by adding map_diff to your list of dependencies in mix.exs:

def deps do
  [{:map_diff, "~> 1.3"}]
end

Changelog

  • 1.3.3 Removes unused dependency 'Tensor'.
  • 1.3.2 Fixes key => values that were unchanged still showing up in the added and removed fields. (Issue #4)
  • 1.3.1 Fixed README and documentation.
  • 1.3.0 Improved doctests, added :added and :removed fields to see without crawling in the depth what was changed at a :map_change.
  • 1.2.0 Comparisons with non-maps is now possible (yielding :primitive_changes).
  • 1.1.1 Refactoring by @andre1sk. Thank you!
  • 1.1.0 Allow comparison of struct fields.
  • 1.0.0 First stable version.

Documentation can be generated with ExDoc and published on HexDocs. Once published, the docs can be found at https://hexdocs.pm/map_diff.

elixir-map_diff's People

Contributors

adambrodzinski avatar dependabot-preview[bot] avatar dependabot-support avatar dependabot[bot] avatar qqwy avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

elixir-map_diff's Issues

I think there is an issue somewhere when array is presented

creative_previous = %{
"CreativeId" => "aamer95b",
"Description" => nil,
"AdvertiserId" => "scr2fkb",
"Availability" => "Available",
"CreatedAtUTC" => "2017-10-26T13:17:37.877",
"CreativeName" => "asdsadsadsasa",
"CreativeType" => "ThirdPartyTag",
"FlightEndDateUTC" => nil,
"FlightStartDateUTC" => nil,
"CreativeAuditStatuses" => [
%{
"AuditStatus" => "pending",
"CreativeAuditor" => "Google",
"ReasonForStatus" => "Submitted for approval"
},
%{
"AuditStatus" => "pending",
"CreativeAuditor" => "AppNexus",
"ReasonForStatus" => nil
},
%{
"AuditStatus" => "pending",
"CreativeAuditor" => "Microsoft",
"ReasonForStatus" => nil
},
%{
"AuditStatus" => "approved",
"CreativeAuditor" => "BrightRoll",
"ReasonForStatus" => "Approved."
},
%{
"AuditStatus" => "pending",
"CreativeAuditor" => "RightMedia",
"ReasonForStatus" => "Pending approval."
}
],
"ThirdPartyTagAttributes" => %{
"AdTag" => "<script id='boo-banner-1296' src='//cdn.boo.live/campaigns/test2/banners/1296/embed.js?click_macro=%%TTD_CLK_ESC%%&referral_url=%%TTD_SITE%%&boostid=393'></script>",
"Width" => 300,
"Height" => 250,
"IsSecurable" => true,
"AdServerName" => "Other",
"MraidVersion" => "Nonmraid",
"AdTechnologyIds" => [],
"LandingPageUrls" => [
"http://www.boo.live"
],
"AdServerCreativeId" => nil,
"RightMediaOfferTypeId" => 3
}
}

current_previous = %{
"CreativeId" => "aamer95b",
"Description" => nil,
"AdvertiserId" => "scr2fkb",
"Availability" => "Available",
"CreatedAtUTC" => "2017-10-26T13:17:37.877",
"CreativeName" => "asdsadsadsasa",
"CreativeType" => "ThirdPartyTag",
"FlightEndDateUTC" => nil,
"FlightStartDateUTC" => nil,
"CreativeAuditStatuses" => [
%{
"AuditStatus" => "pending",
"CreativeAuditor" => "Google",
"ReasonForStatus" => "Google flagged this as needing manual approval."
},
%{
"AuditStatus" => "pending",
"CreativeAuditor" => "AppNexus",
"ReasonForStatus" => nil
},
%{
"AuditStatus" => "pending",
"CreativeAuditor" => "Microsoft",
"ReasonForStatus" => nil
},
%{
"AuditStatus" => "approved",
"CreativeAuditor" => "BrightRoll",
"ReasonForStatus" => "Approved."
},
%{
"AuditStatus" => "pending",
"CreativeAuditor" => "RightMedia",
"ReasonForStatus" => "Pending approval."
}
],
"ThirdPartyTagAttributes" => %{
"AdTag" => "<script id='boo-banner-1296' src='//cdn.boo.live/campaigns/test2/banners/1296/embed.js?click_macro=%%TTD_CLK_ESC%%&referral_url=%%TTD_SITE%%&boostid=393'></script>",
"Width" => 300,
"Height" => 250,
"IsSecurable" => true,
"AdServerName" => "Other",
"MraidVersion" => "Nonmraid",
"AdTechnologyIds" => [],
"LandingPageUrls" => [
"http://www.boo.live"
],
"AdServerCreativeId" => nil,
"RightMediaOfferTypeId" => 3
}
}

result = MapDiff.diff(creative_previous, current_previous)

DateTime comparisons do not work

Here is an example of where DateTime values are not being considered equal, I suppose because under-the-hood a regular == comparison is being done:

# DateTime with no milliseconds
iex(1)> dt_1 = ~U[2020-01-01T00:00:00Z]    
~U[2020-01-01 00:00:00Z]

# DateTime with milliseconds
iex(2)> dt_2 = ~U[2020-01-01T00:00:00.000Z]
~U[2020-01-01 00:00:00.000Z]

# Putting them in maps, they are not equal
iex(3)> MapDiff.diff(%{dt: dt_1}, %{dt: dt_2})
%{
  added: %{dt: ~U[2020-01-01 00:00:00.000Z]},
  changed: :map_change,
  removed: %{dt: ~U[2020-01-01 00:00:00Z]},
  value: %{
    dt: %{
      added: ~U[2020-01-01 00:00:00.000Z],
      changed: :map_change,
      removed: ~U[2020-01-01 00:00:00Z],
      struct_name: DateTime,
      value: %{
        calendar: %{changed: :equal, value: Calendar.ISO},
        day: %{changed: :equal, value: 1},
        hour: %{changed: :equal, value: 0},
        microsecond: %{
          added: {0, 3},
          changed: :primitive_change,
          removed: {0, 0}
        },
        minute: %{changed: :equal, value: 0},
        month: %{changed: :equal, value: 1},
        second: %{changed: :equal, value: 0},
        std_offset: %{changed: :equal, value: 0},
        time_zone: %{changed: :equal, value: "Etc/UTC"},
        utc_offset: %{changed: :equal, value: 0},
        year: %{changed: :equal, value: 2020},
        zone_abbr: %{changed: :equal, value: "UTC"}
      }
    }
  }
}

# Comparing them using == they are not equal
iex(4)> dt_1 == dt_2
false

# Comparing them with DateTime.compare/2 works as expected
iex(5)> DateTime.compare(dt_1, dt_2)
:eq

Possible solutions

  1. Add an option for common structs like this to be compared using their own compare functions:
MapDiff.diff(map1, map2, compare_carefully: [DateTime, NaiveDateTime])
  1. Add a callback to let users do their own comparisons for specific types:
MapDiff.diff(map1, map2, compare: {DateTime, fn a, b -> DateTime.compare(a, b) == :eq end})

If you let me know what your preference is I can prepare a PR.

keys with non changing value ends in the added and removed maps

according to the Readme:

Keys whose values remained equal are thus not listed in these maps

But when I run:

MapDiff.diff(%{a: 1, b: 2, c: 9}, %{a: 3, b: 4, c: 9})

the value for the key c is the same (9) but it is included in the added and removed maps.

%{added: %{a: 3, b: 4, c: 9}, changed: :map_change,
  removed: %{a: 1, b: 2, c: 9},
  value: %{a: %{added: 3, changed: :primitive_change, removed: 1},
    b: %{added: 4, changed: :primitive_change, removed: 2},
    c: %{changed: :equal, value: 9}}}

Is this an issue or I'm missing something?

Thank you for your work in this library!

Way to find out changed value or potentially a bug!

iex(4)> MapDiff.diff(%{a: 1, b: 2, c: %{ d: 5, e: 6 }}, %{a: 4, b: 2, c: %{d: 9, e: 6}})

%{  added: %{a: 4, c: %{d: 9, e: 6}},
  changed: :map_change,
  removed: %{a: 1, c: %{d: 5, e: 6}},  value: %{    a: %{added: 4, changed: :primitive_change, removed: 1},
    b: %{changed: :equal, value: 2},    c: %{
      added: %{d: 9},      changed: :map_change,      removed: %{d: 5},
      value: %{        d: %{added: 9, changed: :primitive_change, removed: 5},        e: %{changed: :equal, value: 6}
      }    }  }
}iex(5)> MapDiff.diff(%{a: 1, b: 2, c: %{ d: 5, e: 6 }}, %{a: 4, b: 2, c: %{d: 9, e: 6}}) |> Map.get(:added)%{a: 4, c: %{d: 9, e: 6}}
iex(6)> 

Since e: 6 is unchanged, is there any way to get only changed values easily ?
I thought :added does that. But maybe i'm misunderstanding something.

Seems to have an issue with Diff

when diffing this

df = MapDiff.diff(
%{"short_name" => nil, "lifetime_pacing" => false,
  "allow_unverified_ecp" => false,
  "code" => "bob-3015b868-4b29-11e7-9eca-6c40088b4566",
  "cpm_bid_type" => "base", "lifetime_budget_imps" => 58500,
  "learn_override_type" => nil, "projected_learn_events" => nil,
  "timezone" => "Etc/UTC", "labels" => nil,
  "start_date" => "2015-05-10 04:15:02", "optimization_version" => "v7",
  "valuation" => nil, "daily_budget_imps" => 58500,
  "roadblock_type" => "no_roadblock", "id" => 16998110,
  "advertiser_id" => 1821428, "roadblock_creatives" => false,
  "supply_type" => "web", "base_bid" => 3.02, "total_days" => 3,
  "optimization_lookback" => nil, "bid_modifier_model" => nil,
  "lifetime_budget" => nil, "campaign_modifiers" => nil, "max_learn_bid" => nil,
  "remaining_days" => nil, "defer_to_li_prediction" => false, "max_bid" => nil,
  "profile_id" => 84609840, "pixels" => nil, "pixel_id" => nil,
  "bid_multiplier" => 1, "daily_budget" => nil, "enable_pacing" => true,
  "supply_type_action" => "include", "comments" => nil,
  "creatives" => [%{"audit_status" => "pending", "code" => nil,
     "format" => "url-js", "height" => 250, "id" => 69777858,
     "is_expired" => false, "is_prohibited" => false,
     "is_self_audited" => false, "name" => "my 300x250 creative",
     "pop_window_maximize" => nil, "state" => "active", "weight" => nil,
     "width" => 300},
   %{"audit_status" => "pending", "code" => nil, "format" => "url-js",
     "height" => 250, "id" => 69777859, "is_expired" => false,
     "is_prohibited" => false, "is_self_audited" => false,
     "name" => "my 300x250 creative 2", "pop_window_maximize" => nil,
     "state" => "active", "weight" => nil, "width" => 300}],
  "impression_limit" => 40000, "creative_id" => 69777858, "state" => "active",
  "name" => "my boost reason", "learn_threshold" => 3,
  "base_cpm_bid_value" => nil, "creative_distribution_type" => nil,
  "priority" => 5, "broker_fees" => nil, "end_date" => "2015-05-12 06:45:12",
  "cadence_type" => "advertiser", "click_url" => nil,
  "allow_safety_pacing" => true, "member_id" => 3102, "bid_model" => nil,
  "require_cookie_for_tracking" => true, "lifetime_pacing_span" => nil,
  "min_bid" => nil, "inventory_type" => "real_time",
  "cadence_modifier_enabled" => false, "bid_margin" => 0,
  "ecp_learn_divisor" => nil, "cpc_payout" => nil, "campaign_type" => "default",
  "cpc_goal" => nil, "allocation_pct" => nil, "lifetime_pacing_pct" => 100,
  "line_item_id" => 4164352},
%{"short_name" => nil, "lifetime_pacing" => false,
  "allow_unverified_ecp" => false,
  "code" => "bob-44785afe-4b29-11e7-9a09-6c40088b4566",
  "cpm_bid_type" => "base", "lifetime_budget_imps" => 18000,
  "learn_override_type" => nil, "projected_learn_events" => nil,
  "timezone" => "Etc/UTC", "labels" => nil,
  "start_date" => "2015-05-10 04:15:02", "optimization_version" => "v7",
  "valuation" => nil, "daily_budget_imps" => 18000,
  "roadblock_type" => "no_roadblock", "id" => 16998110,
  "advertiser_id" => 1821428, "roadblock_creatives" => false,
  "supply_type" => "web", "base_bid" => 3.02, "total_days" => 4,
  "optimization_lookback" => nil, "bid_modifier_model" => nil,
  "lifetime_budget" => nil, "campaign_modifiers" => nil, "max_learn_bid" => nil,
  "remaining_days" => nil, "defer_to_li_prediction" => false, "max_bid" => nil,
  "profile_id" => 84609840, "pixels" => nil, "pixel_id" => nil,
  "bid_multiplier" => 1, "daily_budget" => nil, "enable_pacing" => true,
  "supply_type_action" => "include", "comments" => nil,
  "creatives" => [%{"audit_status" => "pending", "code" => nil,
     "format" => "url-js", "height" => 250, "id" => 69777858,
     "is_expired" => false, "is_prohibited" => false,
     "is_self_audited" => false, "name" => "my 300x250 creative",
     "pop_window_maximize" => nil, "state" => "active", "weight" => nil,
     "width" => 300},
   %{"audit_status" => "pending", "code" => nil, "format" => "url-js",
     "height" => 250, "id" => 69777859, "is_expired" => false,
     "is_prohibited" => false, "is_self_audited" => false,
     "name" => "my 300x250 creative 2", "pop_window_maximize" => nil,
     "state" => "active", "weight" => nil, "width" => 300}],
  "impression_limit" => 40000, "creative_id" => 69777858, "state" => "inactive",
  "name" => "my boost another reason", "learn_threshold" => 3,
  "base_cpm_bid_value" => nil, "creative_distribution_type" => nil,
  "priority" => 5, "broker_fees" => nil, "end_date" => "2015-05-13 06:45:12",
  "cadence_type" => "advertiser", "click_url" => nil,
  "allow_safety_pacing" => true, "member_id" => 3102, "bid_model" => nil,
  "require_cookie_for_tracking" => true, "lifetime_pacing_span" => nil,
  "min_bid" => nil, "inventory_type" => "real_time",
  "cadence_modifier_enabled" => false, "bid_margin" => 0,
  "ecp_learn_divisor" => nil, "cpc_payout" => nil, "campaign_type" => "default",
  "cpc_goal" => nil, "allocation_pct" => nil, "lifetime_pacing_pct" => 100,
  "line_item_id" => 4164352}
)

it is returning

%{added: %{"short_name" => nil, "lifetime_pacing" => false,
    "allow_unverified_ecp" => false,
    "code" => "bob-44785afe-4b29-11e7-9a09-6c40088b4566",
    "cpm_bid_type" => "base", "lifetime_budget_imps" => 18000,
    "learn_override_type" => nil, "projected_learn_events" => nil,
    "timezone" => "Etc/UTC", "labels" => nil,
    "start_date" => "2015-05-10 04:15:02", "optimization_version" => "v7",
    "valuation" => nil, "daily_budget_imps" => 18000,
    "roadblock_type" => "no_roadblock", "id" => 16998110,
    "advertiser_id" => 1821428, "roadblock_creatives" => false,
    "supply_type" => "web", "base_bid" => 3.02, "total_days" => 4,
    "optimization_lookback" => nil, "bid_modifier_model" => nil,
    "lifetime_budget" => nil, "campaign_modifiers" => nil,
    "max_learn_bid" => nil, "remaining_days" => nil,
    "defer_to_li_prediction" => false, "max_bid" => nil,
    "profile_id" => 84609840, "pixels" => nil, "pixel_id" => nil,
    "bid_multiplier" => 1, "daily_budget" => nil, "enable_pacing" => true,
    "supply_type_action" => "include", "comments" => nil,
    "creatives" => [%{"audit_status" => "pending", "code" => nil,
       "format" => "url-js", "height" => 250, "id" => 69777858,
       "is_expired" => false, "is_prohibited" => false,
       "is_self_audited" => false, "name" => "my 300x250 creative",
       "pop_window_maximize" => nil, "state" => "active", "weight" => nil,
       "width" => 300},
     %{"audit_status" => "pending", "code" => nil, "format" => "url-js",
       "height" => 250, "id" => 69777859, "is_expired" => false,
       "is_prohibited" => false, "is_self_audited" => false,
       "name" => "my 300x250 creative 2", "pop_window_maximize" => nil,
       "state" => "active", "weight" => nil, "width" => 300}],
    "impression_limit" => 40000, "creative_id" => 69777858,
    "state" => "inactive", "name" => "my boost another reason",
    "learn_threshold" => 3, "base_cpm_bid_value" => nil,
    "creative_distribution_type" => nil, "priority" => 5, "broker_fees" => nil,
    "end_date" => "2015-05-13 06:45:12", "cadence_type" => "advertiser",
    "click_url" => nil, "allow_safety_pacing" => true, "member_id" => 3102,
    "bid_model" => nil, "require_cookie_for_tracking" => true,
    "lifetime_pacing_span" => nil, "min_bid" => nil,
    "inventory_type" => "real_time", "cadence_modifier_enabled" => false,
    "bid_margin" => 0, "ecp_learn_divisor" => nil, "cpc_payout" => nil,
    "campaign_type" => "default", "cpc_goal" => nil, "allocation_pct" => nil,
    "lifetime_pacing_pct" => 100, "line_item_id" => 4164352},
  changed: :map_change,
  removed: %{"short_name" => nil, "lifetime_pacing" => false,
    "allow_unverified_ecp" => false,
    "code" => "bob-3015b868-4b29-11e7-9eca-6c40088b4566",
    "cpm_bid_type" => "base", "lifetime_budget_imps" => 58500,
    "learn_override_type" => nil, "projected_learn_events" => nil,
    "timezone" => "Etc/UTC", "labels" => nil,
    "start_date" => "2015-05-10 04:15:02", "optimization_version" => "v7",
    "valuation" => nil, "daily_budget_imps" => 58500,
    "roadblock_type" => "no_roadblock", "id" => 16998110,
    "advertiser_id" => 1821428, "roadblock_creatives" => false,
    "supply_type" => "web", "base_bid" => 3.02, "total_days" => 3,
    "optimization_lookback" => nil, "bid_modifier_model" => nil,
    "lifetime_budget" => nil, "campaign_modifiers" => nil,
    "max_learn_bid" => nil, "remaining_days" => nil,
    "defer_to_li_prediction" => false, "max_bid" => nil,
    "profile_id" => 84609840, "pixels" => nil, "pixel_id" => nil,
    "bid_multiplier" => 1, "daily_budget" => nil, "enable_pacing" => true,
    "supply_type_action" => "include", "comments" => nil,
    "creatives" => [%{"audit_status" => "pending", "code" => nil,
       "format" => "url-js", "height" => 250, "id" => 69777858,
       "is_expired" => false, "is_prohibited" => false,
       "is_self_audited" => false, "name" => "my 300x250 creative",
       "pop_window_maximize" => nil, "state" => "active", "weight" => nil,
       "width" => 300},
     %{"audit_status" => "pending", "code" => nil, "format" => "url-js",
       "height" => 250, "id" => 69777859, "is_expired" => false,
       "is_prohibited" => false, "is_self_audited" => false,
       "name" => "my 300x250 creative 2", "pop_window_maximize" => nil,
       "state" => "active", "weight" => nil, "width" => 300}],
    "impression_limit" => 40000, "creative_id" => 69777858, "state" => "active",
    "name" => "my boost reason", "learn_threshold" => 3,
    "base_cpm_bid_value" => nil, "creative_distribution_type" => nil,
    "priority" => 5, "broker_fees" => nil, "end_date" => "2015-05-12 06:45:12",
    "cadence_type" => "advertiser", "click_url" => nil,
    "allow_safety_pacing" => true, "member_id" => 3102, "bid_model" => nil,
    "require_cookie_for_tracking" => true, "lifetime_pacing_span" => nil,
    "min_bid" => nil, "inventory_type" => "real_time",
    "cadence_modifier_enabled" => false, "bid_margin" => 0,
    "ecp_learn_divisor" => nil, "cpc_payout" => nil,
    "campaign_type" => "default", "cpc_goal" => nil, "allocation_pct" => nil,
    "lifetime_pacing_pct" => 100, "line_item_id" => 4164352},
  value: %{"short_name" => %{changed: :equal, value: nil},
    "lifetime_pacing" => %{changed: :equal, value: false},
    "allow_unverified_ecp" => %{changed: :equal, value: false},
    "code" => %{added: "bob-44785afe-4b29-11e7-9a09-6c40088b4566",
      changed: :primitive_change,
      removed: "bob-3015b868-4b29-11e7-9eca-6c40088b4566"},
    "cpm_bid_type" => %{changed: :equal, value: "base"},
    "lifetime_budget_imps" => %{added: 18000, changed: :primitive_change,
      removed: 58500}, "learn_override_type" => %{changed: :equal, value: nil},
    "projected_learn_events" => %{changed: :equal, value: nil},
    "timezone" => %{changed: :equal, value: "Etc/UTC"},
    "labels" => %{changed: :equal, value: nil},
    "start_date" => %{changed: :equal, value: "2015-05-10 04:15:02"},
    "optimization_version" => %{changed: :equal, value: "v7"},
    "valuation" => %{changed: :equal, value: nil},
    "daily_budget_imps" => %{added: 18000, changed: :primitive_change,
      removed: 58500},
    "roadblock_type" => %{changed: :equal, value: "no_roadblock"},
    "id" => %{changed: :equal, value: 16998110},
    "advertiser_id" => %{changed: :equal, value: 1821428},
    "roadblock_creatives" => %{changed: :equal, value: false},
    "supply_type" => %{changed: :equal, value: "web"},
    "base_bid" => %{changed: :equal, value: 3.02},
    "total_days" => %{added: 4, changed: :primitive_change, removed: 3},
    "optimization_lookback" => %{changed: :equal, value: nil},
    "bid_modifier_model" => %{changed: :equal, value: nil},
    "lifetime_budget" => %{changed: :equal, value: nil},
    "campaign_modifiers" => %{changed: :equal, value: nil},
    "max_learn_bid" => %{changed: :equal, value: nil},
    "remaining_days" => %{changed: :equal, value: nil},
    "defer_to_li_prediction" => %{changed: :equal, value: false},
    "max_bid" => %{changed: :equal, value: nil},
    "profile_id" => %{changed: :equal, value: 84609840},
    "pixels" => %{changed: :equal, value: nil},
    "pixel_id" => %{changed: :equal, value: nil},
    "bid_multiplier" => %{changed: :equal, value: 1},
    "daily_budget" => %{changed: :equal, value: nil},
    "enable_pacing" => %{changed: :equal, value: true},
    "supply_type_action" => %{changed: :equal, value: "include"},
    "comments" => %{changed: :equal, value: nil},
    "creatives" => %{changed: :equal,
      value: [%{"audit_status" => "pending", "code" => nil,
         "format" => "url-js", "height" => 250, "id" => 69777858,
         "is_expired" => false, "is_prohibited" => false,
         "is_self_audited" => false, "name" => "my 300x250 creative",
         "pop_window_maximize" => nil, "state" => "active", "weight" => nil,
         "width" => 300},
       %{"audit_status" => "pending", "code" => nil, "format" => "url-js",
         "height" => 250, "id" => 69777859, "is_expired" => false,
         "is_prohibited" => false, "is_self_audited" => false,
         "name" => "my 300x250 creative 2", "pop_window_maximize" => nil,
         "state" => "active", "weight" => nil, "width" => 300}]},
    "impression_limit" => %{changed: :equal, value: 40000},
    "creative_id" => %{changed: :equal, value: 69777858},
    "state" => %{added: "inactive", changed: :primitive_change,
      removed: "active"},
    "name" => %{added: "my boost another reason", changed: :primitive_change,
      removed: "my boost reason"},
    "learn_threshold" => %{changed: :equal, value: 3},
    "base_cpm_bid_value" => %{changed: :equal, value: nil},
    "creative_distribution_type" => %{changed: :equal, value: nil},
    "priority" => %{changed: :equal, value: 5},
    "broker_fees" => %{changed: :equal, value: nil},
    "end_date" => %{added: "2015-05-13 06:45:12", changed: :primitive_change,
      removed: "2015-05-12 06:45:12"},
    "cadence_type" => %{changed: :equal, value: "advertiser"},
    "click_url" => %{changed: :equal, value: nil},
    "allow_safety_pacing" => %{changed: :equal, value: true},
    "member_id" => %{changed: :equal, value: 3102},
    "bid_model" => %{changed: :equal, value: nil},
    "require_cookie_for_tracking" => %{changed: :equal, value: true},
    "lifetime_pacing_span" => %{changed: :equal, value: nil},
    "min_bid" => %{changed: :equal, value: nil},
    "inventory_type" => %{changed: :equal, value: "real_time"},
    "cadence_modifier_enabled" => %{changed: :equal, value: false},
    "bid_margin" => %{changed: :equal, value: 0},
    "ecp_learn_divisor" => %{changed: :equal, value: nil},
    "cpc_payout" => %{changed: :equal, value: nil},
    "campaign_type" => %{changed: :equal, value: "default"},
    "cpc_goal" => %{changed: :equal, value: nil},
    "allocation_pct" => %{changed: :equal, value: nil},
    "lifetime_pacing_pct" => %{changed: :equal, value: 100},
    "line_item_id" => %{changed: :equal, value: 4164352}}}

issues with equality with scientific notation

2100 == 2.1e3
true

MapDiff.diff(%{a: 2100}, %{a: 2.1e3})
%{added: %{a: 2.1e3}, changed: :map_change, removed: %{a: 2100},
value: %{a: %{added: 2.1e3, changed: :primitive_change, removed: 2100}}}

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.